From 2a6b486b5665aa1535adf95c9c13152a32743b5e Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 29 Mar 2024 13:18:24 -0700 Subject: [PATCH] Add `armResourceIdentifier` scalar to azure core (#407) Co-authored-by: Mark Cowlishaw --- .../azure-core-arm-id-2024-2-12-17-36-10.md | 8 ++ .../azure-core-arm-id-2024-2-12-17-36-8.md | 8 ++ .../azure-core-arm-id-2024-2-12-17-36-9.md | 8 ++ docs/howtos/ARM/resource-type.md | 6 +- .../azure-core/reference/data-types.md | 32 +++++ docs/libraries/azure-core/reference/index.mdx | 1 + .../reference/data-types.md | 63 +-------- .../reference/index.mdx | 1 - docs/reference/arm/index.md | 2 +- .../liftr.playfab/playfab.tsp | 4 +- packages/typespec-autorest/src/openapi.ts | 7 + .../test/azure-core-operations.test.ts | 19 --- .../test/azure-core-types.test.ts | 64 +++++++++ .../typespec-azure-core/lib/decorators.tsp | 10 ++ packages/typespec-azure-core/lib/models.tsp | 45 +++++++ .../typespec-azure-core/src/decorators.ts | 58 +++++++- .../types/arm-resource-identifier.test.ts | 125 ++++++++++++++++++ .../lib/customer-managed-keys.tsp | 2 +- .../lib/models.tsp | 37 +----- .../lib/private-links.tsp | 2 +- .../test/openapi-output.test.ts | 14 +- .../test/resource.test.ts | 38 ++---- 22 files changed, 401 insertions(+), 153 deletions(-) create mode 100644 .chronus/changes/azure-core-arm-id-2024-2-12-17-36-10.md create mode 100644 .chronus/changes/azure-core-arm-id-2024-2-12-17-36-8.md create mode 100644 .chronus/changes/azure-core-arm-id-2024-2-12-17-36-9.md create mode 100644 packages/typespec-autorest/test/azure-core-types.test.ts create mode 100644 packages/typespec-azure-core/test/types/arm-resource-identifier.test.ts diff --git a/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-10.md b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-10.md new file mode 100644 index 0000000000..12703a5215 --- /dev/null +++ b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-10.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@azure-tools/typespec-autorest" +--- + +Add support for new `Azure.Core.armResourceManager` scalar diff --git a/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-8.md b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-8.md new file mode 100644 index 0000000000..2e93314d41 --- /dev/null +++ b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-8.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@azure-tools/typespec-azure-core" +--- + +Adding new `armResourceIdentifier` scalar to represent an Arm ID diff --git a/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-9.md b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-9.md new file mode 100644 index 0000000000..7c5c4b9f40 --- /dev/null +++ b/.chronus/changes/azure-core-arm-id-2024-2-12-17-36-9.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: deprecation +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Deprecate `ResourceIdentifier` in favor of new `Azure.Core.armResourceIdentifier` diff --git a/docs/howtos/ARM/resource-type.md b/docs/howtos/ARM/resource-type.md index 998ad29aeb..d80ee2a22c 100644 --- a/docs/howtos/ARM/resource-type.md +++ b/docs/howtos/ARM/resource-type.md @@ -237,7 +237,7 @@ enum EmployeeProvisioningState { scalar EmployeeLevel extends int32; scalar EmployeeResourceId - extends ResourceIdentifier<[ + extends Azure.Core.armResourceIdentifier<[ { type: "Microsoft.HR/employees", } @@ -343,14 +343,14 @@ It is often the case that resources need to reference other resources to provide ```typespec scalar EmployeeResourceId - extends ResourceIdentifier<[ + extends Azure.Core.armResourceIdentifier<[ { type: "Microsoft.HR/employees", } ]>; scalar NetworkInterfaceId - extends ResourceIdentifier<[ + extends Azure.Core.armResourceIdentifier<[ { type: "Microsoft.Network/networkInterfaces", }, diff --git a/docs/libraries/azure-core/reference/data-types.md b/docs/libraries/azure-core/reference/data-types.md index 4584478617..ef2bbafe6f 100644 --- a/docs/libraries/azure-core/reference/data-types.md +++ b/docs/libraries/azure-core/reference/data-types.md @@ -57,6 +57,19 @@ model Azure.Core.AadTokenAuthFlow | tokenUrl | `TokenUrl` | | | scopes | `Scopes` | | +### `ArmResourceIdentifierAllowedResource` {#Azure.Core.ArmResourceIdentifierAllowedResource} + +```typespec +model Azure.Core.ArmResourceIdentifierAllowedResource +``` + +#### Properties + +| Name | Type | Description | +| ------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| type | `string` | The type of resource that is being referred to. For example Microsoft.Network/virtualNetworks or Microsoft.Network/virtualNetworks/subnets. See Example Types for more examples. | +| scopes? | `Core.ArmResourceDeploymentScope[]` | An array of scopes. If not specified, the default scope is ["ResourceGroup"].
See [Allowed Scopes](https://github.com/Azure/autorest/tree/main/docs/extensions#allowed-scopes). | + ### `AzureApiKeyAuthentication` {#Azure.Core.AzureApiKeyAuthentication} Azure API Key Authentication using the "Ocp-Apim-Subscription-Key" hea @@ -466,6 +479,12 @@ Supported versions of Azure.Core TypeSpec building blocks. enum Azure.Core.Versions ``` +### `ArmResourceDeploymentScope` {#Azure.Core.ArmResourceDeploymentScope} + +```typespec +union Azure.Core.ArmResourceDeploymentScope +``` + ### `PollingOptionKind` {#Azure.Core.PollingOptionKind} The available kinds of polling options @@ -482,6 +501,19 @@ Repeatability Result header options union Azure.Core.RepeatabilityResult ``` +### `armResourceIdentifier` {#Azure.Core.armResourceIdentifier} + +A type definition that refers the id to an Azure Resource Manager resource. + +Sample usage: +otherArmId: ResourceIdentifier; +networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> +vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> + +```typespec +scalar Azure.Core.armResourceIdentifier +``` + ### `azureLocation` {#Azure.Core.azureLocation} Represents an Azure geography region where supported resource providers live. diff --git a/docs/libraries/azure-core/reference/index.mdx b/docs/libraries/azure-core/reference/index.mdx index 7a229a0923..d6aeb6abd4 100644 --- a/docs/libraries/azure-core/reference/index.mdx +++ b/docs/libraries/azure-core/reference/index.mdx @@ -84,6 +84,7 @@ npm install --save-peer @azure-tools/typespec-azure-core - [`AadOauth2Auth`](./data-types.md#Azure.Core.AadOauth2Auth) - [`AadTokenAuthFlow`](./data-types.md#Azure.Core.AadTokenAuthFlow) +- [`ArmResourceIdentifierAllowedResource`](./data-types.md#Azure.Core.ArmResourceIdentifierAllowedResource) - [`AzureApiKeyAuthentication`](./data-types.md#Azure.Core.AzureApiKeyAuthentication) - [`ClientRequestIdHeader`](./data-types.md#Azure.Core.ClientRequestIdHeader) - [`ConditionalRequestHeaders`](./data-types.md#Azure.Core.ConditionalRequestHeaders) diff --git a/docs/libraries/azure-resource-manager/reference/data-types.md b/docs/libraries/azure-resource-manager/reference/data-types.md index 239214242c..cf07b414f3 100644 --- a/docs/libraries/azure-resource-manager/reference/data-types.md +++ b/docs/libraries/azure-resource-manager/reference/data-types.md @@ -390,7 +390,7 @@ model Azure.ResourceManager.CustomerManagedKeyEncryption | Name | Type | Description | | ------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | keyEncryptionIdentity? | [`KeyEncryptionIdentity`](./data-types.md#Azure.ResourceManager.KeyEncryptionIdentity) | The type of identity to use. Values can be systemAssignedIdentity, userAssignedIdentity, or delegatedResourceIdentity. | -| userAssignedIdentityResourceId? | [`ResourceIdentifier`](#Azure.ResourceManager.ResourceIdentifier) | User assigned identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity. | +| userAssignedIdentityResourceId? | `Core.armResourceIdentifier` | User assigned identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity. | | federatedClientId? | `Core.uuid` | application client identity to use for accessing key encryption key Url in a different tenant. Ex: f83c6b1b-4d34-47e4-bb34-9d83df58b540 | | delegatedIdentityClientId? | `Core.uuid` | delegated identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity and userAssignedIdentity - internal use only. | @@ -642,9 +642,9 @@ model Azure.ResourceManager.PrivateEndpoint #### Properties -| Name | Type | Description | -| ---- | ----------------------------------------------------------------- | -------------------------------------------- | -| id? | [`ResourceIdentifier`](#Azure.ResourceManager.ResourceIdentifier) | The resource identifier for private endpoint | +| Name | Type | Description | +| ---- | ---------------------------- | -------------------------------------------- | +| id? | `Core.armResourceIdentifier` | The resource identifier for private endpoint | ### `PrivateEndpointConnection` {#Azure.ResourceManager.PrivateEndpointConnection} @@ -860,22 +860,6 @@ model Azure.ResourceManager.ResourceGroupParameter | ----------------- | -------- | ------------------------------------------------------------- | | resourceGroupName | `string` | The name of the resource group. The name is case insensitive. | -### `ResourceIdentifierAllowedResource` {#Azure.ResourceManager.ResourceIdentifierAllowedResource} - -Used in ResourceIdentifier definition to represent a particular type of Azure Resource Manager resource, enabling constraints based on resource type. -See [link](https://github.com/Azure/autorest/tree/main/docs/extensions#schema) - -```typespec -model Azure.ResourceManager.ResourceIdentifierAllowedResource -``` - -#### Properties - -| Name | Type | Description | -| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| type | `string` | The type of resource that is being referred to. For example Microsoft.Network/virtualNetworks or Microsoft.Network/virtualNetworks/subnets. See Example Types for more examples. | -| scopes? | `string[]` | An array of scopes. If not specified, the default scope is ["ResourceGroup"].
See [Allowed Scopes](https://github.com/Azure/autorest/tree/main/docs/extensions#allowed-scopes). | - ### `ResourceInstanceParameters` {#Azure.ResourceManager.ResourceInstanceParameters} The dynamic parameters of a resource instance - pass in the proper base type to indicate @@ -1151,45 +1135,6 @@ Supported versions of Azure.ResourceManager building blocks. enum Azure.ResourceManager.Versions ``` -### `ResourceIdentifier` {#Azure.ResourceManager.ResourceIdentifier} - -A type definition that refers the id to an Azure Resource Manager resource. - -Sample usage: -otherArmId: ResourceIdentifier; -networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> -vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> - -```typespec -scalar Azure.ResourceManager.ResourceIdentifier -``` - -### `ResourceIdentifier` {#Azure.ResourceManager.ResourceIdentifier} - -A type definition that refers the id to an Azure Resource Manager resource. - -Sample usage: -otherArmId: ResourceIdentifier; -networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> -vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> - -```typespec -scalar Azure.ResourceManager.ResourceIdentifier -``` - -### `ResourceIdentifier` {#Azure.ResourceManager.ResourceIdentifier} - -A type definition that refers the id to an Azure Resource Manager resource. - -Sample usage: -otherArmId: ResourceIdentifier; -networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> -vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> - -```typespec -scalar Azure.ResourceManager.ResourceIdentifier -``` - ## Azure.ResourceManager.CommonTypes ### `Versions` {#Azure.ResourceManager.CommonTypes.Versions} diff --git a/docs/libraries/azure-resource-manager/reference/index.mdx b/docs/libraries/azure-resource-manager/reference/index.mdx index 8ebfa767e5..8271cf998c 100644 --- a/docs/libraries/azure-resource-manager/reference/index.mdx +++ b/docs/libraries/azure-resource-manager/reference/index.mdx @@ -163,7 +163,6 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager - [`ProxyResource`](./data-types.md#Azure.ResourceManager.ProxyResource) - [`ResourceGroupLocationResource`](./data-types.md#Azure.ResourceManager.ResourceGroupLocationResource) - [`ResourceGroupParameter`](./data-types.md#Azure.ResourceManager.ResourceGroupParameter) -- [`ResourceIdentifierAllowedResource`](./data-types.md#Azure.ResourceManager.ResourceIdentifierAllowedResource) - [`ResourceInstanceParameters`](./data-types.md#Azure.ResourceManager.ResourceInstanceParameters) - [`ResourceKind`](./data-types.md#Azure.ResourceManager.ResourceKind) - [`ResourceListResult`](./data-types.md#Azure.ResourceManager.ResourceListResult) diff --git a/docs/reference/arm/index.md b/docs/reference/arm/index.md index dc42eeec4f..c1c1090d3e 100644 --- a/docs/reference/arm/index.md +++ b/docs/reference/arm/index.md @@ -35,7 +35,7 @@ The following models are used for different purposes: | Model | Category | Notes | | -------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ArmResource | Base | Defines the base model with common properties for all ARM resources. | -| ResourceIdentifier | Common | A type definition that refers the id to an ARM resource. Sample usage: ` otherArmId: ResourceIdentifier; networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]>` | +| armResourceIdentifier | Common | A type definition that refers the id to an ARM resource. Sample usage: ` otherArmId: ResourceIdentifier; networkId: armResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> vmIds: armResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]>` | | DefaultProvisioningStateProperty | Common | Contains a default provisioningState property to be spread into resource property types. Available values: `Succeeded`, `Failed`, and `Cancelled` | | ManagedServiceIdentity | Common | The managed service identities envelope. | | EntityTag | Common | The eTag property envelope. | diff --git a/packages/samples/specs/resource-manager/liftr.playfab/playfab.tsp b/packages/samples/specs/resource-manager/liftr.playfab/playfab.tsp index 1f55bbd97f..47bb173b20 100644 --- a/packages/samples/specs/resource-manager/liftr.playfab/playfab.tsp +++ b/packages/samples/specs/resource-manager/liftr.playfab/playfab.tsp @@ -24,7 +24,7 @@ interface Operations extends Azure.ResourceManager.Operations {} @doc("A reference to a resource of type title.") @pattern("\\/subscriptions\\/[a-z0-9\\-]+\\/resourceGroups\\/[^\\/]+\\/providers\\/Microsoft\\.PlayFab\\/titles\\/[^\\/]+") scalar TitleReference - extends ResourceIdentifier<[ + extends Azure.Core.armResourceIdentifier<[ { type: "Microsoft.Playfab/titles", } @@ -33,7 +33,7 @@ scalar TitleReference @doc("A reference to a player database resource.") @pattern("\\/subscriptions\\/[a-z0-9\\-]+\\/resourceGroups\\/[^\\/]+\\/providers\\/Microsoft\\.PlayFab\\/playerDatabases\\/[^\\/]+") scalar PlayerDatabaseReference - extends ResourceIdentifier<[ + extends Azure.Core.armResourceIdentifier<[ { type: "Microsoft.Playfab/playerDatabases", } diff --git a/packages/typespec-autorest/src/openapi.ts b/packages/typespec-autorest/src/openapi.ts index b03e7354ac..20a468d398 100644 --- a/packages/typespec-autorest/src/openapi.ts +++ b/packages/typespec-autorest/src/openapi.ts @@ -2,6 +2,7 @@ import { PagedResultMetadata, UnionEnum, extractLroStates, + getArmResourceIdentifierConfig, getAsEmbeddingVector, getLroMetadata, getPagedResult, @@ -1825,6 +1826,12 @@ function createOAPIEmitter( if (getAsEmbeddingVector(program, type as Model) !== undefined) { emitObject["x-ms-embedding-vector"] = true; } + if (type.kind === "Scalar") { + const ext = getArmResourceIdentifierConfig(program, type); + if (ext) { + emitObject["x-ms-arm-id-details"] = ext; + } + } if (extensions) { for (const key of extensions.keys()) { emitObject[key] = extensions.get(key); diff --git a/packages/typespec-autorest/test/azure-core-operations.test.ts b/packages/typespec-autorest/test/azure-core-operations.test.ts index b8452ae1e1..0535b1cc88 100644 --- a/packages/typespec-autorest/test/azure-core-operations.test.ts +++ b/packages/typespec-autorest/test/azure-core-operations.test.ts @@ -138,22 +138,3 @@ describe("typespec-autorest: Azure.Core.ResourceOperations", () => { checkParams(params, "/widgets"); }); }); - -describe("typespec-autorest: Azure.Core concepts", () => { - it("defines embedding vector models", async () => { - const result = await openApiFor(` - ${wrapperCode} - model Foo is Azure.Core.EmbeddingVector; - `); - const model = result.definitions["Foo"]; - deepStrictEqual(model, { - type: "array", - description: "A vector embedding frequently used in similarity search.", - "x-ms-embedding-vector": true, - items: { - type: "integer", - format: "int32", - }, - }); - }); -}); diff --git a/packages/typespec-autorest/test/azure-core-types.test.ts b/packages/typespec-autorest/test/azure-core-types.test.ts new file mode 100644 index 0000000000..f927153451 --- /dev/null +++ b/packages/typespec-autorest/test/azure-core-types.test.ts @@ -0,0 +1,64 @@ +import { deepStrictEqual } from "assert"; +import { describe, it } from "vitest"; +import { openApiFor } from "./test-host.js"; + +const base = ` +@service +@useDependency(Azure.Core.Versions.v1_0_Preview_2) +namespace MyService; +`; + +describe("EmbeddingVector", () => { + it("defines embedding vector models", async () => { + const result = await openApiFor(` + ${base} + model Foo is Azure.Core.EmbeddingVector; + `); + const model = result.definitions["Foo"]; + deepStrictEqual(model, { + type: "array", + description: "A vector embedding frequently used in similarity search.", + "x-ms-embedding-vector": true, + items: { + type: "integer", + format: "int32", + }, + }); + }); +}); + +describe("armResourceIdentifier", () => { + it("without config", async () => { + const result = await openApiFor(` + ${base} + scalar Foo extends Azure.Core.armResourceIdentifier; + `); + const model = result.definitions["Foo"]; + deepStrictEqual(model, { + type: "string", + format: "arm-id", + description: "A type definition that refers the id to an Azure Resource Manager resource.", + }); + }); + + it("with config", async () => { + const result = await openApiFor(` + ${base} + scalar Foo extends Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>; + `); + const model = result.definitions["Foo"]; + deepStrictEqual(model, { + type: "string", + format: "arm-id", + description: "A type definition that refers the id to an Azure Resource Manager resource.", + "x-ms-arm-id-details": { + allowedResources: [ + { + scopes: ["tenant", "resourceGroup"], + type: "Microsoft.RP/type", + }, + ], + }, + }); + }); +}); diff --git a/packages/typespec-azure-core/lib/decorators.tsp b/packages/typespec-azure-core/lib/decorators.tsp index bfa9cec4b6..8e8ed1bd89 100644 --- a/packages/typespec-azure-core/lib/decorators.tsp +++ b/packages/typespec-azure-core/lib/decorators.tsp @@ -199,6 +199,16 @@ namespace Azure { entity: TypeSpec.Reflection.Model, type: TypeSpec.Reflection.Scalar ); + + model ArmResourceIdentifierConfigOptions { + allowedResources: ArmResourceIdentifierAllowedResource[]; + } + + /** Configuration for the armResourceIdentifier scalar */ + extern dec armResourceIdentifierConfig( + target: Scalar, + options: ArmResourceIdentifierConfigOptions + ); } } } diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp index 53ee6507c5..f821cb50b9 100644 --- a/packages/typespec-azure-core/lib/models.tsp +++ b/packages/typespec-azure-core/lib/models.tsp @@ -355,3 +355,48 @@ scalar eTag extends string; * ``` */ scalar azureLocation extends string; + +/** + * A type definition that refers the id to an Azure Resource Manager resource. + * + * Sample usage: + * otherArmId: ResourceIdentifier; + * networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> + * vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> + * @template AllowedResourceTypes An array of allowed resource types for the resource reference + */ +@doc("A type definition that refers the id to an Azure Resource Manager resource.") +@format("arm-id") +@Azure.Core.Foundations.Private.armResourceIdentifierConfig({ + allowedResources: AllowedResourceTypes, +}) +scalar armResourceIdentifier + extends string; + +// Consider replacing `*` +union ArmResourceDeploymentScope { + "tenant", + "subscription", + "resourceGroup", + "managementGroup", + "extension", +} + +alias AllArmResourceDeploymentScopes = [ + "tenant", + "subscription", + "resourceGroup", + "managementGroup", + "extension" +]; + +model ArmResourceIdentifierAllowedResource { + /** The type of resource that is being referred to. For example Microsoft.Network/virtualNetworks or Microsoft.Network/virtualNetworks/subnets. See Example Types for more examples. */ + type: string; + + /** + * An array of scopes. If not specified, the default scope is ["ResourceGroup"]. + * See [Allowed Scopes](https://github.com/Azure/autorest/tree/main/docs/extensions#allowed-scopes). + */ + scopes?: ArmResourceDeploymentScope[]; +} diff --git a/packages/typespec-azure-core/src/decorators.ts b/packages/typespec-azure-core/src/decorators.ts index 2ea6e27cc0..6a99799176 100644 --- a/packages/typespec-azure-core/src/decorators.ts +++ b/packages/typespec-azure-core/src/decorators.ts @@ -25,6 +25,7 @@ import { setTypeSpecNamespace, StringLiteral, Type, + typespecTypeToJson, Union, UnionVariant, walkPropertiesInherited, @@ -1219,6 +1220,60 @@ export function getAsEmbeddingVector( return program.stateMap(embeddingVectorKey).get(model); } +const armResourceIdentifierConfigKey = createStateSymbol("armResourceIdentifierConfig"); + +export interface ArmResourceIdentifierConfig { + readonly allowedResources: readonly ArmResourceIdentifierAllowedResource[]; +} + +export type ArmResourceDeploymentScope = + | "Tenant" + | "Subscription" + | "ResourceGroup" + | "ManagementGroup" + | "Extension"; + +export interface ArmResourceIdentifierAllowedResource { + /** The type of resource that is being referred to. For example Microsoft.Network/virtualNetworks or Microsoft.Network/virtualNetworks/subnets. See Example Types for more examples. */ + readonly type: string; + + /** + * An array of scopes. If not specified, the default scope is ["ResourceGroup"]. + * See [Allowed Scopes](https://github.com/Azure/autorest/tree/main/docs/extensions#allowed-scopes). + */ + readonly scopes?: ArmResourceDeploymentScope[]; +} + +/** @internal */ +export function $armResourceIdentifierConfig( + context: DecoratorContext, + entity: Scalar, + config: Type +) { + if (config.kind !== "Model") return; + const prop = config.properties.get("allowedResources"); + if (prop === undefined || prop.type.kind !== "Tuple") return; + const [data, diagnostics] = typespecTypeToJson( + prop.type, + context.getArgumentTarget(0)! + ); + context.program.reportDiagnostics(diagnostics); + + if (data) { + context.program + .stateMap(armResourceIdentifierConfigKey) + .set(entity, { allowedResources: data }); + } +} + +/** Returns the config attached to an armResourceIdentifierScalar */ +export function getArmResourceIdentifierConfig( + program: Program, + entity: Scalar +): ArmResourceIdentifierConfig { + return program.stateMap(armResourceIdentifierConfigKey).get(entity); +} + setTypeSpecNamespace("Foundations", $omitKeyProperties, $requestParameter, $responseProperty); setTypeSpecNamespace( "Foundations.Private", @@ -1227,5 +1282,6 @@ setTypeSpecNamespace( $ensureResourceType, $needsRoute, $ensureVerb, - $embeddingVector + $embeddingVector, + $armResourceIdentifierConfig ); diff --git a/packages/typespec-azure-core/test/types/arm-resource-identifier.test.ts b/packages/typespec-azure-core/test/types/arm-resource-identifier.test.ts new file mode 100644 index 0000000000..cf43691eb1 --- /dev/null +++ b/packages/typespec-azure-core/test/types/arm-resource-identifier.test.ts @@ -0,0 +1,125 @@ +import { ModelProperty, Scalar } from "@typespec/compiler"; +import { BasicTestRunner } from "@typespec/compiler/testing"; +import { strictEqual } from "assert"; +import { beforeEach, describe, expect, it } from "vitest"; +import { getArmResourceIdentifierConfig } from "../../src/decorators.js"; +import { createAzureCoreTestRunner } from "../test-host.js"; + +let runner: BasicTestRunner; +beforeEach(async () => { + runner = await createAzureCoreTestRunner(); +}); + +describe("when used as ref", () => { + async function compileAsRef(ref: string): Promise { + const { prop } = (await runner.compile(` + model Test { + @test prop: ${ref}; + } + `)) as { prop: ModelProperty }; + + const type = prop.type; + strictEqual(type.kind, "Scalar"); + return type; + } + + it("use without config", async () => { + const type = await compileAsRef("armResourceIdentifier"); + expect(getArmResourceIdentifierConfig(runner.program, type)).toBeUndefined(); + }); + + it("use with single type and no scopes", async () => { + const type = await compileAsRef(`armResourceIdentifier<[{type:"Microsoft.RP/type"}]>`); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + }, + ], + }); + }); + + it("use with single type and scopes", async () => { + const type = await compileAsRef( + `armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>` + ); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + scopes: ["tenant", "resourceGroup"], + }, + ], + }); + }); + + it("use multiple single type and scopes", async () => { + const type = await compileAsRef( + `armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>` + ); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + scopes: ["tenant", "resourceGroup"], + }, + { type: "Microsoft.RP/type2", scopes: ["tenant", "resourceGroup"] }, + ], + }); + }); +}); + +describe("when used as scalar extends", () => { + async function compileOnScalar(ref: string): Promise { + const { test } = (await runner.compile(` + @test scalar test extends ${ref}; + `)) as { test: Scalar }; + + return test.baseScalar!; + } + + it("use without config", async () => { + const type = await compileOnScalar("armResourceIdentifier"); + expect(getArmResourceIdentifierConfig(runner.program, type)).toBeUndefined(); + }); + + it("use with single type and no scopes", async () => { + const type = await compileOnScalar(`armResourceIdentifier<[{type:"Microsoft.RP/type"}]>`); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + }, + ], + }); + }); + + it("use with single type and scopes", async () => { + const type = await compileOnScalar( + `armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>` + ); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + scopes: ["tenant", "resourceGroup"], + }, + ], + }); + }); + + it("use multiple single type and scopes", async () => { + const type = await compileOnScalar( + `armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>` + ); + expect(getArmResourceIdentifierConfig(runner.program, type)).toEqual({ + allowedResources: [ + { + type: "Microsoft.RP/type", + scopes: ["tenant", "resourceGroup"], + }, + { type: "Microsoft.RP/type2", scopes: ["tenant", "resourceGroup"] }, + ], + }); + }); +}); diff --git a/packages/typespec-azure-resource-manager/lib/customer-managed-keys.tsp b/packages/typespec-azure-resource-manager/lib/customer-managed-keys.tsp index c0b3306fdc..72478522f2 100644 --- a/packages/typespec-azure-resource-manager/lib/customer-managed-keys.tsp +++ b/packages/typespec-azure-resource-manager/lib/customer-managed-keys.tsp @@ -30,7 +30,7 @@ model CustomerManagedKeyEncryption { keyEncryptionIdentity?: KeyEncryptionIdentity; @doc("User assigned identity to use for accessing key encryption key Url. Ex: /subscriptions/fa5fc227-a624-475e-b696-cdd604c735bc/resourceGroups//providers/Microsoft.ManagedIdentity/userAssignedIdentities/myId. Mutually exclusive with identityType systemAssignedIdentity.") - userAssignedIdentityResourceId?: ResourceIdentifier<[ + userAssignedIdentityResourceId?: Azure.Core.armResourceIdentifier<[ { type: "Microsoft.ManagedIdentity/userAssignedIdentities"; } diff --git a/packages/typespec-azure-resource-manager/lib/models.tsp b/packages/typespec-azure-resource-manager/lib/models.tsp index 73cd9da3d6..590c36525e 100644 --- a/packages/typespec-azure-resource-manager/lib/models.tsp +++ b/packages/typespec-azure-resource-manager/lib/models.tsp @@ -90,40 +90,13 @@ model ProviderNamespace { //#region Common Azure Resource Manager type definitions /** - * A type definition that refers the id to an Azure Resource Manager resource. - * - * Sample usage: - * otherArmId: ResourceIdentifier; - * networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]> - * vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]> - * @template AllowedResourceTypes An array of allowed resource types for the resource reference + * Use Azure.Core.armResourceIdentifier */ -@doc("A type definition that refers the id to an Azure Resource Manager resource.") -@format("arm-id") -@OpenAPI.extension( - "x-ms-arm-id-details", - { - allowedResources: AllowedResourceTypes, - } -) -scalar ResourceIdentifier - extends string; +#deprecated "Use Azure.Core.armResourceIdentifier instead." +alias ResourceIdentifier = Azure.Core.armResourceIdentifier; -/** - * Used in ResourceIdentifier definition to represent a particular type of Azure Resource Manager resource, enabling constraints based on resource type. - * See [link](https://github.com/Azure/autorest/tree/main/docs/extensions#schema) - */ -@doc("Optional definition represents a particular type of Azure Resource Manager resource which can be referred to by a ResourceIdentifier.") -model ResourceIdentifierAllowedResource { - @doc("The type of resource that is being referred to. For example Microsoft.Network/virtualNetworks or Microsoft.Network/virtualNetworks/subnets. See Example Types for more examples.") - type: string; - - @doc(""" - An array of scopes. If not specified, the default scope is ["ResourceGroup"]. - See [Allowed Scopes](https://github.com/Azure/autorest/tree/main/docs/extensions#allowed-scopes). - """) - scopes?: string[] = []; -} +#deprecated "Use ArmResourceIdentifierAllowedResource instead." +alias ResourceIdentifierAllowedResource = ArmResourceIdentifierAllowedResource; /** * Standard terminal provisioning state of resource type. You can spread into your diff --git a/packages/typespec-azure-resource-manager/lib/private-links.tsp b/packages/typespec-azure-resource-manager/lib/private-links.tsp index 1548fdb41e..b082dfc903 100644 --- a/packages/typespec-azure-resource-manager/lib/private-links.tsp +++ b/packages/typespec-azure-resource-manager/lib/private-links.tsp @@ -28,7 +28,7 @@ namespace Azure.ResourceManager; ) model PrivateEndpoint { /** The resource identifier for private endpoint */ - id?: ResourceIdentifier<[ + id?: Azure.Core.armResourceIdentifier<[ { type: "Microsoft.Network/privateEndpoints"; } diff --git a/packages/typespec-azure-resource-manager/test/openapi-output.test.ts b/packages/typespec-azure-resource-manager/test/openapi-output.test.ts index 574bd47ca0..0ffa11089f 100644 --- a/packages/typespec-azure-resource-manager/test/openapi-output.test.ts +++ b/packages/typespec-azure-resource-manager/test/openapi-output.test.ts @@ -8,6 +8,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { const openapi = await openApiFor( `@armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; interface Operations extends Azure.ResourceManager.Operations {} @@ -22,7 +23,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { @doc("Foo properties") model FooResourceProperties { @doc("I am a simple Resource Identifier") - simpleArmId: ResourceIdentifier; + simpleArmId: Azure.Core.armResourceIdentifier; @doc("The provisioning State") provisioningState: ResourceState; @@ -48,7 +49,6 @@ describe("typespec-azure-resource-manager: autorest output", () => { type: "string", description: "I am a simple Resource Identifier", format: "arm-id", - "x-ms-arm-id-details": { allowedResources: [] }, }); }); @@ -56,6 +56,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { const openapi = await openApiFor( `@armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; interface Operations extends Azure.ResourceManager.Operations {} @@ -70,7 +71,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { @doc("Foo properties") model FooResourceProperties { @doc("I am a Resource Identifier with type only") - armIdWithType: ResourceIdentifier<[{type:"Microsoft.RP/type"}]>; + armIdWithType: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type"}]>; @doc("The provisioning State") provisioningState: ResourceState; @@ -104,6 +105,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { const openapi = await openApiFor( `@armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; interface Operations extends Azure.ResourceManager.Operations {} @@ -118,7 +120,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { @doc("Foo properties") model FooResourceProperties { @doc("I am a a Resource Identifier with type and scopes") - armIdWithTypeAndScope: ResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>; + armIdWithTypeAndScope: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>; @doc("The provisioning State") provisioningState: ResourceState; @@ -154,6 +156,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { const openapi = await openApiFor( `@armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; interface Operations extends Azure.ResourceManager.Operations {} @@ -168,7 +171,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { @doc("Foo properties") model FooResourceProperties { @doc("I am a a Resource Identifier with multiple types and scopes") - armIdWithMultipleTypeAndScope: ResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>; + armIdWithMultipleTypeAndScope: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>; @doc("The provisioning State") provisioningState: ResourceState; @@ -210,6 +213,7 @@ describe("typespec-azure-resource-manager: autorest output", () => { const openapi = await openApiFor( `@armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; interface Operations extends Azure.ResourceManager.Operations {} diff --git a/packages/typespec-azure-resource-manager/test/resource.test.ts b/packages/typespec-azure-resource-manager/test/resource.test.ts index f2f3a35411..fecdafec12 100644 --- a/packages/typespec-azure-resource-manager/test/resource.test.ts +++ b/packages/typespec-azure-resource-manager/test/resource.test.ts @@ -508,51 +508,33 @@ describe("typespec-azure-resource-manager: ARM resource model", () => { strictEqual(foo.armProviderNamespace, "Microsoft.Test"); }); - it("resources with ResourceIdentifier property types", async () => { + it("resources with armResourceIdentifier property types", async () => { const { program, diagnostics } = await checkFor(` @armProviderNamespace @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @useDependency(Azure.Core.Versions.v1_0_Preview_2) namespace Microsoft.Test; - interface Operations extends Azure.ResourceManager.Operations {} - - @doc("The state of the resource") enum ResourceState { - @doc(".") Succeeded, - @doc(".") Canceled, - @doc(".") Failed + Succeeded, + Canceled, + Failed } - @doc("Foo properties") model FooResourceProperties { - @doc("I am a simple Resource Identifier") - simpleArmId: ResourceIdentifier; - - @doc("I am a Resource Identifier with type only") - armIdWithType: ResourceIdentifier<[{type:"Microsoft.RP/type"}]>; - - @doc("I am a a Resource Identifier with type and scopes") - armIdWithTypeAndScope: ResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>; - - @doc("I am a a Resource Identifier with multiple types and scopes") - armIdWithMultipleTypeAndScope: ResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>; - - @doc("The provisioning State") + simpleArmId: Azure.Core.armResourceIdentifier; + armIdWithType: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type"}]>; + armIdWithTypeAndScope: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}]>; + armIdWithMultipleTypeAndScope: Azure.Core.armResourceIdentifier<[{type:"Microsoft.RP/type", scopes:["tenant", "resourceGroup"]}, {type:"Microsoft.RP/type2", scopes:["tenant", "resourceGroup"]}]>; provisioningState: ResourceState; } - @doc("Foo resource") model FooResource is TrackedResource { - @doc("Foo name") @key("fooName") @segment("foos") @path name: string; } - - @armResourceOperations - interface Foos extends TrackedResourceOperations { - } `); const resources = getArmResources(program); @@ -574,7 +556,7 @@ describe("typespec-azure-resource-manager: ARM resource model", () => { ]; armIds.forEach(function (id) { const armIdProp = getResourcePropertyProperties(foo, id); - strictEqual((armIdProp?.type as Model).name, "ResourceIdentifier"); + strictEqual((armIdProp?.type as Model).name, "armResourceIdentifier"); }); });