Skip to content

Commit 8144b5f

Browse files
authored
Rule to check if Tags are defined as Top-level property (#710)
* Add TagsAreTopLevelPropertiesOnly new rule * addressed comments * addressed comments * addressed the comment by removing the change log file * addressed comments
1 parent f514622 commit 8144b5f

File tree

6 files changed

+326
-8
lines changed

6 files changed

+326
-8
lines changed

docs/rules.md

+6
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,12 @@ Validates that system data is not defined in the properties bag, but rather as a
12201220

12211221
Please refer to [system-data-in-properties-bag.md](./system-data-in-properties-bag.md) for details.
12221222

1223+
### TagsAreNotAllowedForProxyResources
1224+
1225+
`tags` should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead.
1226+
1227+
Please refer to [tags-are-not-allowed-for-proxy-resources.md](./tags-are-not-allowed-for-proxy-resources.md) for details.
1228+
12231229
### TenantLevelAPIsNotAllowed
12241230

12251231
Tenant level APIs are strongly discouraged and subscription or resource group level APIs are preferred instead. The reason for this guidance is that tenant level APIs have a really broad scope and blast radius. We permit APIs to be at this broad scope under rare conditions. Some ARM feature sets also do not cover tenant level APIs such as the use of AFEC. Additionally, if you intend to bypass the standard RBAC constructs and make the APIs unauthorized, you will need an approval from the PAS team before the open API spec can be merged.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# TagsAreNotAllowedForProxyResources
2+
3+
## Category
4+
5+
ARM Error
6+
7+
## Applies to
8+
9+
ARM OpenAPI(swagger) specs
10+
11+
## Related ARM Guideline Code
12+
13+
- RPC-Put-V1-31
14+
15+
## Description
16+
17+
`tags` should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead.
18+
19+
## How to fix the violation
20+
21+
Either remove the `tags` from the properties bag or consider using a Tracked resource, which supports top-level `tags`.
22+
23+
### Valid/Good Example
24+
25+
```json
26+
"definitions": {
27+
"Resource": {
28+
"type": "object",
29+
"properties": {
30+
"type": "object",
31+
// top-level properties
32+
"tags": {
33+
"type": "object",
34+
"additionalProperties": {
35+
"type": "object",
36+
"params": {
37+
"type": "boolean",
38+
},
39+
}
40+
},
41+
"location": {
42+
"type": "string"
43+
}
44+
}
45+
},
46+
}
47+
```
48+
49+
### Invalid/Bad Example
50+
51+
```json
52+
"definitions": {
53+
"Resource": {
54+
"type": "string",
55+
"properties": {
56+
"type": "object",
57+
"properties": {
58+
"type": "object",
59+
// Nested Properties
60+
"tags": {
61+
"type": "object",
62+
"additionalProperties": {
63+
"type": "object",
64+
"params": {
65+
"type": "boolean",
66+
},
67+
}
68+
},
69+
},
70+
}
71+
}
72+
}
73+
```

packages/rulesets/generated/spectral/az-arm.js

+44-8
Original file line numberDiff line numberDiff line change
@@ -2081,7 +2081,7 @@ const patchBodyParameters = (parameters, _opts, paths) => {
20812081
return errors;
20822082
};
20832083

2084-
const ERROR_MESSAGE$1 = "A patch request body must only contain properties present in the corresponding put request body, and must contain at least one of the properties.";
2084+
const ERROR_MESSAGE$2 = "A patch request body must only contain properties present in the corresponding put request body, and must contain at least one of the properties.";
20852085
const PARAM_IN_BODY = (paramObject) => paramObject.in === "body";
20862086
const PATCH = "patch";
20872087
const PUT = "put";
@@ -2116,7 +2116,7 @@ const patchPropertiesCorrespondToPutProperties = (pathItem, _opts, ctx) => {
21162116
const patchBodyPropertiesNotInPutBody = _.differenceWith(patchBodyProperties[0], putBodyProperties[0], _.isEqual);
21172117
if (patchBodyPropertiesNotInPutBody.length > 0) {
21182118
patchBodyPropertiesNotInPutBody.forEach((missingProperty) => errors.push({
2119-
message: `${Object.keys(missingProperty)[0]} property in patch body is not present in the corresponding put body. ` + ERROR_MESSAGE$1,
2119+
message: `${Object.keys(missingProperty)[0]} property in patch body is not present in the corresponding put body. ` + ERROR_MESSAGE$2,
21202120
path: path,
21212121
}));
21222122
return errors;
@@ -2704,31 +2704,54 @@ const skuValidation = (skuSchema, opts, paths) => {
27042704

27052705
const SYSTEM_DATA_CAMEL = "systemData";
27062706
const SYSTEM_DATA_UPPER_CAMEL = "SystemData";
2707-
const PROPERTIES = "properties";
2708-
const ERROR_MESSAGE = "System data must be defined as a top-level property, not in the properties bag.";
2707+
const PROPERTIES$1 = "properties";
2708+
const ERROR_MESSAGE$1 = "System data must be defined as a top-level property, not in the properties bag.";
27092709
const systemDataInPropertiesBag = (definition, _opts, ctx) => {
27102710
const properties = getProperties(definition);
27112711
const path = deepFindObjectKeyPath(properties, SYSTEM_DATA_CAMEL);
27122712
if (path.length > 0) {
27132713
return [
27142714
{
2715-
message: ERROR_MESSAGE,
2716-
path: _.concat(ctx.path, PROPERTIES, path[0]),
2715+
message: ERROR_MESSAGE$1,
2716+
path: _.concat(ctx.path, PROPERTIES$1, path[0]),
27172717
},
27182718
];
27192719
}
27202720
const pathForUpperCamelCase = deepFindObjectKeyPath(properties, SYSTEM_DATA_UPPER_CAMEL);
27212721
if (pathForUpperCamelCase.length > 0) {
27222722
return [
27232723
{
2724-
message: ERROR_MESSAGE,
2725-
path: _.concat(ctx.path, PROPERTIES, pathForUpperCamelCase[0]),
2724+
message: ERROR_MESSAGE$1,
2725+
path: _.concat(ctx.path, PROPERTIES$1, pathForUpperCamelCase[0]),
27262726
},
27272727
];
27282728
}
27292729
return [];
27302730
};
27312731

2732+
const TAGS = "tags";
2733+
const PROPERTIES = "properties";
2734+
const NestedPROPERTIES = "properties";
2735+
const ERROR_MESSAGE = "Tags should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead.";
2736+
const tagsAreNotAllowedForProxyResources = (definition, _opts, ctx) => {
2737+
const properties = getProperties(definition);
2738+
const errors = [];
2739+
if ("tags" in properties && !("location" in properties)) {
2740+
errors.push({
2741+
message: ERROR_MESSAGE,
2742+
path: _.concat(ctx.path, PROPERTIES, TAGS),
2743+
});
2744+
}
2745+
const deepPropertiesTags = deepFindObjectKeyPath(definition.properties.properties, TAGS);
2746+
if (deepPropertiesTags.length > 0) {
2747+
errors.push({
2748+
message: ERROR_MESSAGE,
2749+
path: _.concat(ctx.path, PROPERTIES, NestedPROPERTIES, deepPropertiesTags[0]),
2750+
});
2751+
}
2752+
return errors;
2753+
};
2754+
27322755
const tenantLevelAPIsNotAllowed = (pathItems, _opts, ctx) => {
27332756
if (pathItems === null || typeof pathItems !== "object") {
27342757
return [];
@@ -3525,6 +3548,19 @@ const ruleset = {
35253548
function: consistentResponseSchemaForPut,
35263549
},
35273550
},
3551+
TagsAreNotAllowedForProxyResources: {
3552+
rpcGuidelineCode: "RPC-Put-V1-31",
3553+
description: "Tags should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead.",
3554+
severity: "error",
3555+
stagingOnly: true,
3556+
message: "{{error}}",
3557+
resolved: true,
3558+
formats: [oas2],
3559+
given: ["$.definitions.*.properties^"],
3560+
then: {
3561+
function: tagsAreNotAllowedForProxyResources,
3562+
},
3563+
},
35283564
ParametersInPost: {
35293565
rpcGuidelineCode: "RPC-POST-V1-05",
35303566
description: "For a POST action parameters MUST be in the payload and not in the URI.",

packages/rulesets/src/spectral/az-arm.ts

+16
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import responseSchemaSpecifiedForSuccessStatusCode from "./functions/response-sc
4747
import { securityDefinitionsStructure } from "./functions/security-definitions-structure"
4848
import skuValidation from "./functions/sku-validation"
4949
import { systemDataInPropertiesBag } from "./functions/system-data-in-properties-bag"
50+
import { tagsAreNotAllowedForProxyResources } from "./functions/tags-are-not-allowed-for-proxy-resources"
5051
import { tenantLevelAPIsNotAllowed } from "./functions/tenant-level-apis-not-allowed"
5152
import { trackedExtensionResourcesAreNotAllowed } from "./functions/tracked-extension-resources-are-not-allowed"
5253
import trackedResourceTagsPropertyInRequest from "./functions/trackedresource-tags-property-in-request"
@@ -736,6 +737,21 @@ const ruleset: any = {
736737
},
737738
},
738739

740+
// RPC Code: RPC-Put-V1-31
741+
TagsAreNotAllowedForProxyResources: {
742+
rpcGuidelineCode: "RPC-Put-V1-31",
743+
description: "Tags should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead.",
744+
severity: "error",
745+
stagingOnly: true,
746+
message: "{{error}}",
747+
resolved: true,
748+
formats: [oas2],
749+
given: ["$.definitions.*.properties^"],
750+
then: {
751+
function: tagsAreNotAllowedForProxyResources,
752+
},
753+
},
754+
739755
///
740756
/// ARM RPC rules for Post patterns
741757
///
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import _ from "lodash"
2+
import { deepFindObjectKeyPath, getProperties } from "./utils"
3+
4+
const TAGS = "tags"
5+
const PROPERTIES = "properties"
6+
const NestedPROPERTIES = "properties"
7+
const ERROR_MESSAGE = "Tags should not be specified in the properties bag for proxy resources. Consider using a Tracked resource instead."
8+
9+
export const tagsAreNotAllowedForProxyResources = (definition: any, _opts: any, ctx: any) => {
10+
const properties = getProperties(definition)
11+
const errors = []
12+
13+
if ("tags" in properties && !("location" in properties)) {
14+
errors.push({
15+
message: ERROR_MESSAGE,
16+
path: _.concat(ctx.path, PROPERTIES, TAGS),
17+
})
18+
}
19+
20+
const deepPropertiesTags = deepFindObjectKeyPath(definition.properties.properties, TAGS)
21+
if (deepPropertiesTags.length > 0) {
22+
errors.push({
23+
message: ERROR_MESSAGE,
24+
path: _.concat(ctx.path, PROPERTIES, NestedPROPERTIES, deepPropertiesTags[0]),
25+
})
26+
}
27+
28+
return errors
29+
}

0 commit comments

Comments
 (0)