Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { createRule, paramMessage, type Model, type ModelProperty } from "@typespec/compiler";
import { getArmResources } from "@azure-tools/typespec-azure-resource-manager";
import { createRule, paramMessage, type Model, type ModelProperty } from "@typespec/compiler";

export const tagsAreNotAllowedForProxyResourcesRule = createRule({
name: "tags-are-not-allowed-for-proxy-resources",
description:
"Proxy ARM resources must not declare a top-level tags property in their properties bag.",
"Proxy ARM resources must not declare a tags property on the resource envelope or in their properties bag.",
severity: "warning",
messages: {
default:
paramMessage`Proxy resource '${"resourceName"}' must not declare top-level \`tags\` in its properties bag. Use a tracked resource if tags are required.`,
default: paramMessage`Proxy resource '${"resourceName"}' must not declare \`tags\` on its resource envelope or in its properties bag. Use a tracked resource if tags are required.`,
},
create(context) {
return {
Expand All @@ -18,18 +17,20 @@ export const tagsAreNotAllowedForProxyResourcesRule = createRule({
continue;
}

const resourceTags = getPropertyInHierarchy(armResource.typespecType, "tags");
const propertiesModel = getResourcePropertiesModel(armResource.typespecType);
const tagsProperty = propertiesModel && getPropertyInHierarchy(propertiesModel, "tags");
if (!tagsProperty) {
continue;
}
const propertiesTags = propertiesModel && getPropertyInHierarchy(propertiesModel, "tags");

context.reportDiagnostic({
target: tagsProperty,
format: {
resourceName: armResource.name,
},
});
for (const tagsProperty of [resourceTags, propertiesTags]) {
if (tagsProperty) {
context.reportDiagnostic({
target: tagsProperty,
format: {
resourceName: armResource.name,
},
});
}
}
}
},
};
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"violation": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import "../../lib/imports.tsp";

using TypeSpec.Http;
using TypeSpec.Rest;
using TypeSpec.Versioning;
using Azure.ResourceManager;

@armProviderNamespace
@service(#{ title: "Test Service" })
@versioned(Versions)
namespace Microsoft.TestService;

enum Versions {
@useDependency(Azure.ResourceManager.CommonTypes.Versions.v5)
v2024_01_01: "2024-01-01",
}

model Gadget is ProxyResource<GadgetProperties> {
@key("gadgetName")
@segment("gadgets")
@path
name: string;

#suppress "@azure-tools/typespec-azure-resource-manager/arm-no-record" "Record needed to produce tags object in swagger"
tags?: Record<string>;
}

model GadgetProperties {
description?: string;
}

@armResourceOperations
interface Gadgets {
get is ArmResourceRead<Gadget>;
createOrUpdate is ArmResourceCreateOrReplaceSync<Gadget>;
delete is ArmResourceDeleteSync<Gadget>;
listBySubscription is ArmListBySubscription<Gadget>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
{
"swagger": "2.0",
"info": {
"title": "Test Service",
"version": "2024-01-01",
"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": "Gadgets"
}
],
"paths": {
"/subscriptions/{subscriptionId}/providers/Microsoft.TestService/gadgets": {
"get": {
"operationId": "Gadgets_ListBySubscription",
"tags": [
"Gadgets"
],
"description": "List Gadget resources by subscription ID",
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/SubscriptionIdParameter"
}
],
"responses": {
"200": {
"description": "Azure operation completed successfully.",
"schema": {
"$ref": "#/definitions/GadgetListResult"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse"
}
}
},
"x-ms-pageable": {
"nextLinkName": "nextLink"
}
}
},
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.TestService/gadgets/{gadgetName}": {
"get": {
"operationId": "Gadgets_Get",
"tags": [
"Gadgets"
],
"description": "Get a Gadget",
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ResourceGroupNameParameter"
},
{
"name": "gadgetName",
"in": "path",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "Azure operation completed successfully.",
"schema": {
"$ref": "#/definitions/Gadget"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse"
}
}
}
},
"put": {
"operationId": "Gadgets_CreateOrUpdate",
"tags": [
"Gadgets"
],
"description": "Create a Gadget",
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ResourceGroupNameParameter"
},
{
"name": "gadgetName",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "resource",
"in": "body",
"description": "Resource create parameters.",
"required": true,
"schema": {
"$ref": "#/definitions/Gadget"
}
}
],
"responses": {
"200": {
"description": "Resource 'Gadget' update operation succeeded",
"schema": {
"$ref": "#/definitions/Gadget"
}
},
"201": {
"description": "Resource 'Gadget' create operation succeeded",
"schema": {
"$ref": "#/definitions/Gadget"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse"
}
}
}
},
"delete": {
"operationId": "Gadgets_Delete",
"tags": [
"Gadgets"
],
"description": "Delete a Gadget",
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ResourceGroupNameParameter"
},
{
"name": "gadgetName",
"in": "path",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "Resource deleted successfully."
},
"204": {
"description": "Resource does not exist."
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse"
}
}
}
}
}
},
"definitions": {
"Gadget": {
"type": "object",
"description": "Concrete proxy resource types can be created by aliasing this type using a specific property type.",
"properties": {
"properties": {
"$ref": "#/definitions/GadgetProperties",
"description": "The resource-specific properties for this resource."
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"allOf": [
{
"$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ProxyResource"
}
]
},
"GadgetListResult": {
"type": "object",
"description": "The response of a Gadget list operation.",
"properties": {
"value": {
"type": "array",
"description": "The Gadget items on this page",
"items": {
"$ref": "#/definitions/Gadget"
}
},
"nextLink": {
"type": "string",
"format": "uri",
"description": "The link to the next page of items"
}
},
"required": [
"value"
]
},
"GadgetProperties": {
"type": "object",
"properties": {
"description": {
"type": "string"
}
}
}
},
"parameters": {}
}
Loading