diff --git a/.chronus/changes/fix-remove-cmk-v5v6-refs-2026-6-2-0-31-57.md b/.chronus/changes/fix-remove-cmk-v5v6-refs-2026-6-2-0-31-57.md new file mode 100644 index 0000000000..be8e8f34ad --- /dev/null +++ b/.chronus/changes/fix-remove-cmk-v5v6-refs-2026-6-2-0-31-57.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Add Foundations V4 encryption types (CustomerManagedKeyEncryptionV4, KeyEncryptionKeyIdentityV4, KeyEncryptionKeyIdentityTypeV4) to replace deprecated CustomerManagedKeyEncryption. Add new @armCommonDefinitionExcluded decorator and arm-common-definition-excluded linter rule to warn when CustomerManagedKeyEncryption is used directly in service specifications outside the Azure.ResourceManager namespace. Deprecate CustomerManagedKeyEncryption in favor of the Encryption type. diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts index 375f3b3137..c914f6032a 100644 --- a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts @@ -334,6 +334,18 @@ export type GenericResourceInternalDecorator = ( target: Model, ) => DecoratorValidatorCallbacks | void; +/** + * Marks an Azure Resource Manager common type as excluded from direct use in + * service specifications. Types decorated with this decorator should not be + * referenced directly in service specs; use the equivalent Foundations type instead. + * @param replacementTypeName The name of the replacement type to use instead + */ +export type ArmCommonDefinitionExcludedDecorator = ( + context: DecoratorContext, + target: Model, + replacementTypeName: string, +) => DecoratorValidatorCallbacks | void; + export type AzureResourceManagerPrivateDecorators = { resourceParameterBaseFor: ResourceParameterBaseForDecorator; resourceBaseParametersOf: ResourceBaseParametersOfDecorator; @@ -357,4 +369,5 @@ export type AzureResourceManagerPrivateDecorators = { legacyExtensionResourceOperation: LegacyExtensionResourceOperationDecorator; validateCommonTypesVersionForResource: ValidateCommonTypesVersionForResourceDecorator; genericResourceInternal: GenericResourceInternalDecorator; + armCommonDefinitionExcluded: ArmCommonDefinitionExcludedDecorator; }; diff --git a/packages/typespec-azure-resource-manager/lib/common-types/customer-managed-keys.tsp b/packages/typespec-azure-resource-manager/lib/common-types/customer-managed-keys.tsp index fc310b23f6..33ca931fdc 100644 --- a/packages/typespec-azure-resource-manager/lib/common-types/customer-managed-keys.tsp +++ b/packages/typespec-azure-resource-manager/lib/common-types/customer-managed-keys.tsp @@ -49,6 +49,7 @@ model KeyEncryptionKeyIdentity { /** Customer-managed key encryption properties for the resource. */ @added(Versions.v4) +@Azure.ResourceManager.Private.armCommonDefinitionExcluded("Azure.ResourceManager.CommonTypes.Encryption") model CustomerManagedKeyEncryption { /** All identity configuration for Customer-managed key settings defining which identity should be used to auth to Key Vault. */ keyEncryptionKeyIdentity?: KeyEncryptionKeyIdentity; diff --git a/packages/typespec-azure-resource-manager/lib/foundations/arm.foundations.tsp b/packages/typespec-azure-resource-manager/lib/foundations/arm.foundations.tsp index 4b369ab268..55afe73933 100644 --- a/packages/typespec-azure-resource-manager/lib/foundations/arm.foundations.tsp +++ b/packages/typespec-azure-resource-manager/lib/foundations/arm.foundations.tsp @@ -3,6 +3,7 @@ import "@typespec/rest"; import "./backcompat.tsp"; import "./deprecation.tsp"; +import "./encryption.tsp"; import "../common-types/common-types.tsp"; import "../decorators.tsp"; import "../responses.tsp"; diff --git a/packages/typespec-azure-resource-manager/lib/foundations/encryption.tsp b/packages/typespec-azure-resource-manager/lib/foundations/encryption.tsp new file mode 100644 index 0000000000..8c41c26ce3 --- /dev/null +++ b/packages/typespec-azure-resource-manager/lib/foundations/encryption.tsp @@ -0,0 +1,41 @@ +using Azure.Core; + +namespace Azure.ResourceManager.Foundations; + +/** The type of identity to use. */ +union KeyEncryptionKeyIdentityTypeV4 { + /** System assigned identity */ + SystemAssignedIdentity: "systemAssignedIdentity", + + /** User assigned identity */ + UserAssignedIdentity: "userAssignedIdentity", + + /** Delegated identity */ + DelegatedResourceIdentity: "delegatedResourceIdentity", + + string, +} + +/** All identity configuration for Customer-managed key settings defining which identity should be used to auth to Key Vault. */ +model KeyEncryptionKeyIdentityV4 { + /** The type of identity to use. Values can be systemAssignedIdentity, userAssignedIdentity, or delegatedResourceIdentity. */ + identityType?: KeyEncryptionKeyIdentityTypeV4; + + /** 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?: Azure.Core.armResourceIdentifier; + + /** application client identity to use for accessing key encryption key Url in a different tenant. Ex: f83c6b1b-4d34-47e4-bb34-9d83df58b540 */ + federatedClientId?: 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. */ + delegatedIdentityClientId?: uuid; +} + +/** Customer-managed key encryption properties for the resource. */ +model CustomerManagedKeyEncryptionV4 { + /** All identity configuration for Customer-managed key settings defining which identity should be used to auth to Key Vault. */ + keyEncryptionKeyIdentity?: KeyEncryptionKeyIdentityV4; + + /** key encryption key Url, versioned or non-versioned. Ex: https://contosovault.vault.azure.net/keys/contosokek/562a4bb76b524a1493a6afe8e536ee78 or https://contosovault.vault.azure.net/keys/contosokek. */ + keyEncryptionKeyUrl?: string; +} diff --git a/packages/typespec-azure-resource-manager/lib/private.decorators.tsp b/packages/typespec-azure-resource-manager/lib/private.decorators.tsp index ba7a873837..78e76d40bd 100644 --- a/packages/typespec-azure-resource-manager/lib/private.decorators.tsp +++ b/packages/typespec-azure-resource-manager/lib/private.decorators.tsp @@ -241,3 +241,11 @@ extern dec validateCommonTypesVersionForResource( * `model MyResource is GenericResource;` */ extern dec genericResourceInternal(target: Model); + +/** + * Marks an Azure Resource Manager common type as excluded from direct use in + * service specifications. Types decorated with this decorator should not be + * referenced directly in service specs; use the equivalent Foundations type instead. + * @param replacementTypeName The name of the replacement type to use instead + */ +extern dec armCommonDefinitionExcluded(target: Model, replacementTypeName: valueof string); diff --git a/packages/typespec-azure-resource-manager/src/lib.ts b/packages/typespec-azure-resource-manager/src/lib.ts index c05143c507..9e004ea9f1 100644 --- a/packages/typespec-azure-resource-manager/src/lib.ts +++ b/packages/typespec-azure-resource-manager/src/lib.ts @@ -131,6 +131,12 @@ export const $lib = createTypeSpecLibrary({ default: paramMessage`The specified common-types version '${"version"}' is not valid for ${"resourceName"} resources. Please use version ${"requiredVersion"} or later of common-types.`, }, }, + "no-deprecated-common-types": { + severity: "warning", + messages: { + default: paramMessage`The type '${"typeName"}' is an internal Azure Resource Manager common type and should not be used directly in service specifications. Use the type '${"replacementType"}' instead.`, + }, + }, }, }); diff --git a/packages/typespec-azure-resource-manager/src/linter.ts b/packages/typespec-azure-resource-manager/src/linter.ts index 1a027a612a..1776bef73c 100644 --- a/packages/typespec-azure-resource-manager/src/linter.ts +++ b/packages/typespec-azure-resource-manager/src/linter.ts @@ -1,4 +1,5 @@ import { defineLinter } from "@typespec/compiler"; +import { noDeprecatedCommonTypesRule } from "./rules/no-deprecated-common-types.js"; import { armCommonTypesVersionRule } from "./rules/arm-common-types-version.js"; import { armCustomResourceNoKey } from "./rules/arm-custom-resource-no-key.js"; import { armCustomResourceUsageDiscourage } from "./rules/arm-custom-resource-usage-discourage.js"; @@ -40,6 +41,7 @@ const rules = [ armNoRecordRule, armNoPathCasingConflictsRule, armCommonTypesVersionRule, + noDeprecatedCommonTypesRule, armDeleteResponseCodesRule, armPutResponseCodesRule, armPostResponseCodesRule, diff --git a/packages/typespec-azure-resource-manager/src/private.decorators.ts b/packages/typespec-azure-resource-manager/src/private.decorators.ts index 2ddc905560..59bfb2b327 100644 --- a/packages/typespec-azure-resource-manager/src/private.decorators.ts +++ b/packages/typespec-azure-resource-manager/src/private.decorators.ts @@ -47,6 +47,7 @@ import { } from "../generated-defs/Azure.ResourceManager.Extension.Private.js"; import { ArmBodyRootDecorator, + ArmCommonDefinitionExcludedDecorator, ArmRenameListByOperationDecorator, ArmResourceInternalDecorator, ArmResourcePropertiesOptionalityDecorator, @@ -202,6 +203,23 @@ export function isGenericResource(program: Program, target: Model): boolean { return false; } +const [getArmCommonDefinitionExcludedState, setArmCommonDefinitionExcludedState] = useStateMap< + Model, + string +>(ArmStateKeys.armCommonDefinitionExcluded); + +const $armCommonDefinitionExcluded: ArmCommonDefinitionExcludedDecorator = ( + context: DecoratorContext, + target: Model, + replacementTypeName: string, +) => { + setArmCommonDefinitionExcludedState(context.program, target, replacementTypeName); +}; + +export function getArmCommonDefinitionExcluded(program: Program, target: Model): string | undefined { + return getArmCommonDefinitionExcludedState(program, target); +} + const $omitIfEmpty: OmitIfEmptyDecorator = ( context: DecoratorContext, entity: Model, @@ -1086,6 +1104,7 @@ export const $decorators = { builtInResourceOperation: $builtInResourceOperation, validateCommonTypesVersionForResource: $validateCommonTypesVersionForResource, genericResourceInternal: $genericResourceInternal, + armCommonDefinitionExcluded: $armCommonDefinitionExcluded, } satisfies AzureResourceManagerPrivateDecorators, "Azure.ResourceManager.Extension.Private": { builtInResource: $builtInResource, diff --git a/packages/typespec-azure-resource-manager/src/rules/no-deprecated-common-types.ts b/packages/typespec-azure-resource-manager/src/rules/no-deprecated-common-types.ts new file mode 100644 index 0000000000..242749f48b --- /dev/null +++ b/packages/typespec-azure-resource-manager/src/rules/no-deprecated-common-types.ts @@ -0,0 +1,43 @@ +import { Model, ModelProperty, createRule, getNamespaceFullName, paramMessage } from "@typespec/compiler"; +import { getArmCommonDefinitionExcluded } from "../private.decorators.js"; + +/** + * Rule that prevents direct usage of deprecated ARM common types + * in service specifications outside the Azure.ResourceManager library. + */ +export const noDeprecatedCommonTypesRule = createRule({ + name: "no-deprecated-common-types", + severity: "warning", + description: + "Verify deprecated common types are not used directly in service specifications.", + url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-deprecated-common-types", + messages: { + default: paramMessage`The type '${"typeName"}' is an internal Azure Resource Manager common type and should not be used directly in service specifications. Use the type '${"replacementType"}' instead.`, + }, + create(context) { + return { + modelProperty: (property: ModelProperty) => { + const type = property.type; + if (type.kind !== "Model") return; + + // Check if the property type is a deprecated ARM common type + const replacementType = getArmCommonDefinitionExcluded(context.program, type as Model); + if (!replacementType) return; + + // Allow usage within Azure.ResourceManager namespace + const containingNamespace = property.model?.namespace; + if ( + containingNamespace && + getNamespaceFullName(containingNamespace).startsWith("Azure.ResourceManager") + ) { + return; + } + + context.reportDiagnostic({ + format: { typeName: (type as Model).name, replacementType }, + target: property, + }); + }, + }; + }, +}); diff --git a/packages/typespec-azure-resource-manager/src/state.ts b/packages/typespec-azure-resource-manager/src/state.ts index 3405f023ab..1c9b58d6c4 100644 --- a/packages/typespec-azure-resource-manager/src/state.ts +++ b/packages/typespec-azure-resource-manager/src/state.ts @@ -34,6 +34,7 @@ export const ArmStateKeys = { // private.decorator.ts azureResourceBase: azureResourceManagerCreateStateSymbol("azureResourceBase"), renamePathParameters: azureResourceManagerCreateStateSymbol("renamePathParameters"), + armCommonDefinitionExcluded: azureResourceManagerCreateStateSymbol("armCommonDefinitionExcluded"), // commontypes.private.decorators.ts armCommonDefinitions: azureResourceManagerCreateStateSymbol("armCommonDefinitions"), diff --git a/packages/typespec-azure-resource-manager/test/rules/no-deprecated-common-types.test.ts b/packages/typespec-azure-resource-manager/test/rules/no-deprecated-common-types.test.ts new file mode 100644 index 0000000000..8d79f7a3ed --- /dev/null +++ b/packages/typespec-azure-resource-manager/test/rules/no-deprecated-common-types.test.ts @@ -0,0 +1,85 @@ +import { Tester } from "#test/tester.js"; +import { + LinterRuleTester, + TesterInstance, + createLinterRuleTester, +} from "@typespec/compiler/testing"; +import { beforeEach, it } from "vitest"; + +import { noDeprecatedCommonTypesRule } from "../../src/rules/no-deprecated-common-types.js"; + +let runner: TesterInstance; +let tester: LinterRuleTester; + +beforeEach(async () => { + runner = await Tester.createInstance(); + tester = createLinterRuleTester( + runner, + noDeprecatedCommonTypesRule, + "@azure-tools/typespec-azure-resource-manager", + ); +}); + +it("emits diagnostic when using CustomerManagedKeyEncryption directly in a user spec", async () => { + await tester + .expect( + ` + @service(#{ title: "Test" }) + @versioned(Microsoft.Contoso.Versions) + @armProviderNamespace + namespace Microsoft.Contoso; + + enum Versions { + @armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v4) + v4; + } + + @added(Microsoft.Contoso.Versions.v4) + model EncryptionConfig { + customerManagedKey: Azure.ResourceManager.CommonTypes.CustomerManagedKeyEncryption; + } + `, + ) + .toEmitDiagnostics({ + code: "@azure-tools/typespec-azure-resource-manager/no-deprecated-common-types", + }); +}); + +it("does not emit diagnostic when using CustomerManagedKeyEncryption via Encryption wrapper", async () => { + await tester + .expect( + ` + @service(#{ title: "Test" }) + @versioned(Microsoft.Contoso.Versions) + @armProviderNamespace + namespace Microsoft.Contoso; + + enum Versions { + @armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v4) + v4; + } + + @added(Microsoft.Contoso.Versions.v4) + model ResourceProperties { + encryption?: Azure.ResourceManager.CommonTypes.Encryption; + } + `, + ) + .toBeValid(); +}); + +it("does not emit diagnostic when using Foundations.CustomerManagedKeyEncryptionV4", async () => { + await tester + .expect( + ` + @armProviderNamespace + @service + namespace Microsoft.Contoso; + + model EncryptionConfig { + customerManagedKey: Azure.ResourceManager.Foundations.CustomerManagedKeyEncryptionV4; + } + `, + ) + .toBeValid(); +}); diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/rules/no-deprecated-common-types.md b/website/src/content/docs/docs/libraries/azure-resource-manager/rules/no-deprecated-common-types.md new file mode 100644 index 0000000000..4c67dac71d --- /dev/null +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/rules/no-deprecated-common-types.md @@ -0,0 +1,47 @@ +--- +title: no-deprecated-common-types +--- + +```text title="Full name" +@azure-tools/typespec-azure-resource-manager/no-deprecated-common-types +``` + +Verify that deprecated common types are not referenced directly in user service specs. Use the replacement type suggested in the diagnostic message. + +#### ❌ Incorrect + +```tsp +@armProviderNamespace +@service +namespace Microsoft.Contoso; + +model EncryptionConfig { + customerManagedKey: Azure.ResourceManager.CommonTypes.CustomerManagedKeyEncryption; +} +``` + +#### ✅ Correct + +Use the `Encryption` wrapper type which is the recommended approach: + +```tsp +@armProviderNamespace +@service +namespace Microsoft.Contoso; + +model ResourceProperties { + encryption?: Azure.ResourceManager.CommonTypes.Encryption; +} +``` + +Or use the equivalent type from `Azure.ResourceManager.Foundations`: + +```tsp +@armProviderNamespace +@service +namespace Microsoft.Contoso; + +model EncryptionConfig { + customerManagedKey: Azure.ResourceManager.Foundations.CustomerManagedKeyEncryptionV4; +} +```