Skip to content
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
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.

Original file line number Diff line number Diff line change
Expand Up @@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not use internal?

@markcowl markcowl Jun 3, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had forgotten we could do that. I think we should phase this in, with emitting the diagnostic in this version and making the type internal in next version. This makes a bit better transition if some spec is in development that uses this type. thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah didn't pay attention it was added toe existing type. What about deprecation to simplify

* @param replacementTypeName The name of the replacement type to use instead
*/
export type ArmCommonDefinitionExcludedDecorator = (
context: DecoratorContext,
target: Model,
Comment thread
markcowl marked this conversation as resolved.
replacementTypeName: string,
) => DecoratorValidatorCallbacks | void;

export type AzureResourceManagerPrivateDecorators = {
resourceParameterBaseFor: ResourceParameterBaseForDecorator;
resourceBaseParametersOf: ResourceBaseParametersOfDecorator;
Expand All @@ -357,4 +369,5 @@ export type AzureResourceManagerPrivateDecorators = {
legacyExtensionResourceOperation: LegacyExtensionResourceOperationDecorator;
validateCommonTypesVersionForResource: ValidateCommonTypesVersionForResourceDecorator;
genericResourceInternal: GenericResourceInternalDecorator;
armCommonDefinitionExcluded: ArmCommonDefinitionExcludedDecorator;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -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/<resource group>/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/<resource group>/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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
6 changes: 6 additions & 0 deletions packages/typespec-azure-resource-manager/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
},
},
},
});

Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-azure-resource-manager/src/linter.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -40,6 +41,7 @@ const rules = [
armNoRecordRule,
armNoPathCasingConflictsRule,
armCommonTypesVersionRule,
noDeprecatedCommonTypesRule,
armDeleteResponseCodesRule,
armPutResponseCodesRule,
armPostResponseCodesRule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
} from "../generated-defs/Azure.ResourceManager.Extension.Private.js";
import {
ArmBodyRootDecorator,
ArmCommonDefinitionExcludedDecorator,
ArmRenameListByOperationDecorator,
ArmResourceInternalDecorator,
ArmResourcePropertiesOptionalityDecorator,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1086,6 +1104,7 @@ export const $decorators = {
builtInResourceOperation: $builtInResourceOperation,
validateCommonTypesVersionForResource: $validateCommonTypesVersionForResource,
genericResourceInternal: $genericResourceInternal,
armCommonDefinitionExcluded: $armCommonDefinitionExcluded,
} satisfies AzureResourceManagerPrivateDecorators,
"Azure.ResourceManager.Extension.Private": {
builtInResource: $builtInResource,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
markcowl marked this conversation as resolved.
*/
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,
});
},
};
},
});
1 change: 1 addition & 0 deletions packages/typespec-azure-resource-manager/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
});
Original file line number Diff line number Diff line change
@@ -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;
}
```
Loading