Azure / bicep

Bicep is a declarative language for describing and deploying Azure resources
MIT License
3.26k stars 754 forks source link

Bicep linter emits BCP059 (The name "description" is not a function) for description decorator #15150

Open asilverman opened 1 month ago

asilverman commented 1 month ago

Bicep version run bicep --version via the Bicep CLI, az bicep version via the AZ CLI or via VS code by navigating to the extensions tab and searching for Bicep image

Describe the bug The Bicep linter is complaining about the description decorator, the documentation says the below is valid syntax image

To Reproduce

Create a new file with the following contents:

targetScope = 'managementGroup'
param policyAssignmentDisplayName string
param policyAssignmentId string
param oldPolicyAssignmentId string
param policyDefinitionId string

@description('Target Management Group')
param targetManagementGroup string

@description('An array of the allowed locations.')
param allowedLocations array
param customParams object = {}
param roleDefinitionIds array = []
param timestamp string = utcNow()
param subscriptionId string = '<subs-id to host the deployment script>'
param resourceGroup string = '<resource group to host the deployment script>'
param enforcementMode string = 'Default'
param scriptContent string
param description string = 'Default Description'

var mgScope = tenantResourceId('Microsoft.Management/managementGroups', targetManagementGroup)
var policyParams = customParams
var checkOldAssignment_var = 'checkOldAssignment_${timestamp}'
var updateOldAssignment_var = 'updateOldAssignment_${timestamp}'
var regionSelector = {
  name: 'selectedLocations'
  selectors: [
    {
      kind: 'resourceLocation'
      in: allowedLocations
    }
  ]
}
var resourceSelectors = (contains(allowedLocations, 'global')
  ? []
  : [
      regionSelector
    ])

Additional context The documentation for this error is not very informative, a more clear description would help: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-core-diagnostics#BCP059 image

If you instead paste the following contents, the problem goes away:

targetScope = 'managementGroup'
param policyAssignmentDisplayName string
param policyAssignmentId string
param oldPolicyAssignmentId string
param policyDefinitionId string

@description('Target Management Group')
param targetManagementGroup string

@description('An array of the allowed locations.')
param allowedLocations array
param customParams object = {}

var mgScope = tenantResourceId('Microsoft.Management/managementGroups', targetManagementGroup)
var policyParams = customParams

var regionSelector = {
  name: 'selectedLocations'
  selectors: [
    {
      kind: 'resourceLocation'
      in: allowedLocations
    }
  ]
}
var resourceSelectors = (contains(allowedLocations, 'global')
  ? []
  : [
      regionSelector
    ])

image

Content is generated from 'Paste JSON as Bicep' VSCode command on the following content:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "policyAssignmentDisplayName": {
            "type": "string"
        },
        "policyAssignmentId": {
            "type": "string"
        },
        "oldPolicyAssignmentId": {
            "type": "string"
        },
        "policyDefinitionId": {
            "type": "string"
        },
        "targetManagementGroup": {
            "type": "string",
            "metadata": {
                "description": "Target Management Group"
            }
        },
        "allowedLocations": {
            "type": "array",
            "metadata": {
                "description": "An array of the allowed locations."
            }
        },
        "customParams": {
            "type": "object",
            "defaultValue": {}
        },
        "roleDefinitionIds": {
            "type": "array",
            "defaultValue": []
        },
        "timestamp": {
            "type": "string",
            "defaultValue": "[utcNow()]"
        },
        "subscriptionId": {
            "type": "string",
            "defaultValue": "<subs-id to host the deployment script>"
        },
        "resourceGroup": {
            "type": "string",
            "defaultValue": "<resource group to host the deployment script>"
        },
        "enforcementMode": {
            "type": "string",
            "defaultValue": "Default"
        },
        "scriptContent": {
            "type": "string"
        },
        "description": {
            "type": "string",
            "defaultValue": "Default Description"
        }
    },
    "variables": {
        "mgScope": "[tenantResourceId('Microsoft.Management/managementGroups', parameters('targetManagementGroup'))]",
        "policyParams": "[parameters('customParams')]",
        "checkOldAssignment": "[concat('checkOldAssignment', '_', parameters('timestamp'))]",
        "updateOldAssignment": "[concat('updateOldAssignment', '_', parameters('timestamp'))]",
        "regionSelector": {
            "name": "selectedLocations",
            "selectors": [
                {
                    "kind": "resourceLocation",
                    "in": "[parameters('allowedLocations')]"
                }
            ]
        },
        "resourceSelectors": "[if(contains(parameters('allowedLocations'), 'global'), createArray(), createArray(variables('regionSelector')))]"
    },
    "resources": [
        {
            "condition": "[not(empty(parameters('oldPolicyAssignmentId')))]",
            "name": "[variables('checkOldAssignment')]",
            "type": "Microsoft.Resources/deployments",
            "subscriptionId": "[parameters('subscriptionId')]",
            "resourceGroup": "[parameters('resourceGroup')]",
            "apiVersion": "2021-04-01",
            "properties": {
                "mode": "Incremental",
                "expressionEvaluationOptions": {
                    "scope": "Inner"
                },
                "parameters": {
                    "oldPolicyAssignment": {
                        "value": "[if(empty(parameters('oldPolicyAssignmentId')), createObject(),reference(extensionResourceId(variables('mgScope'), 'Microsoft.Authorization/policyAssignments', parameters('oldPolicyAssignmentId')), '2022-06-01', 'Full'))]"
                    },
                    "scriptName": {
                        "value": "[concat('CheckOldAssignment', '_', parameters('timestamp'))]"
                    },
                    "newLocations": {
                        "value": "[parameters('allowedLocations')]"
                    },
                    "scriptContent": {
                        "value": "[parameters('scriptContent')]"
                    }
                },
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {

                        "oldPolicyAssignment": {
                            "type": "object"
                        },
                        "scriptName": {
                            "type": "string"
                        },
                        "newLocations": {
                            "type": "array"
                        },
                        "scriptContent": {
                            "type": "string"
                        }

                    },
                    "variables": {
                        "checkCondition": "[not(empty(parameters('oldPolicyAssignment')))]",
                        "oldResourceSelectors": "[if(contains(parameters('oldPolicyAssignment').properties,'resourceSelectors'), parameters('oldPolicyAssignment').properties.resourceSelectors, createArray())]"
                    },
                    "resources": [
                        {
                            "condition": "[variables('checkCondition')]",
                            "type": "Microsoft.Resources/deploymentScripts",
                            "apiVersion": "2020-10-01",
                            "kind": "AzurePowerShell",
                            "name": "[parameters('scriptName')]",
                            "location": "[resourceGroup().location]",
                            "properties": {
                                "azPowerShellVersion": "6.4",
                                "environmentVariables": [
                                    {
                                        "name": "oldResourceSelectors",
                                        "value": "[string(if(empty(variables('oldResourceSelectors')), createArray(), variables('oldResourceSelectors')))]"
                                    },
                                    {
                                        "name": "newLocations",
                                        "value": "[string(parameters('newLocations'))]"
                                    }

                                ],
                                "scriptContent": "[parameters('scriptContent')]",
                                "cleanupPreference": "Always",
                                "retentionInterval": "PT1H"
                            }
                        }
                    ],
                    "outputs": {
                        "oldAssignment": {
                            "value": "[parameters('oldPolicyAssignment')]",
                            "type": "object"
                        },
                        "updatedResourceSelectors": {
                            "value": "[json(reference(parameters('scriptName')).outputs.text)]",
                            "condition": "[variables('checkCondition')]",
                            "type": "array"
                        }
                    }
                }
            }
        },
        {
            "condition": "[not(empty(parameters('oldPolicyAssignmentId')))]",
            "name": "[variables('updateOldAssignment')]",
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2021-04-01",
            "location": "[deployment().location]",
            "properties": {
                "mode": "Incremental",
                "expressionEvaluationOptions": {
                    "scope": "Inner"
                },
                "parameters": {
                    "oldPolicyAssignmentId": {
                        "value": "[parameters('oldPolicyAssignmentId')]"
                    },
                    "oldPolicyAssignment": {
                        "value": "[if(empty(parameters('oldPolicyAssignmentId')), createObject(), reference(variables('checkOldAssignment'), '2021-04-01').outputs.oldAssignment.value)]"
                    },
                    "updatedResourceSelectors": {
                        "value": "[if(empty(parameters('oldPolicyAssignmentId')), createObject(),reference(variables('checkOldAssignment'), '2021-04-01').outputs.updatedResourceSelectors.value)]"
                    }
                },
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {
                        "oldPolicyAssignmentId": {
                            "type": "string"
                        },
                        "oldPolicyAssignment": {
                            "type": "object"
                        },
                        "updatedResourceSelectors": {
                            "type": "array"
                        }

                    },
                    "variables": {
                        "updateCondition": "[not(empty(parameters('oldPolicyAssignment')))]"

                    },
                    "resources": [
                        {
                            "condition": "[variables('updateCondition')]",
                            "type": "Microsoft.Authorization/policyAssignments",
                            "name": "[parameters('oldPolicyAssignmentId')]",
                            "apiVersion": "[parameters('oldPolicyAssignment').apiVersion]",
                            "location": "[parameters('oldPolicyAssignment').location]",
                            "identity": "[parameters('oldPolicyAssignment').identity]",
                            "properties": {
                                "displayName": "[parameters('oldPolicyAssignment').properties.displayName]",
                                "scope": "[parameters('oldPolicyAssignment').properties.scope]",
                                "enforcementMode": "[parameters('oldPolicyAssignment').properties.enforcementMode]",
                                "policyDefinitionId": "[parameters('oldPolicyAssignment').properties.policyDefinitionId]",
                                "parameters": "[parameters('oldPolicyAssignment').properties.parameters]",
                                "resourceSelectors": "[parameters('updatedResourceSelectors')]"
                            }
                        }
                    ],
                    "outputs": {
                        "updatedOldAssignment": {
                            "value": "[reference(parameters('oldPolicyAssignmentId'))]",
                            "condition": "[variables('updateCondition')]",
                            "type": "object"
                        }
                    }
                }
            }
        },
        {
            "type": "Microsoft.Authorization/policyAssignments",
            "dependsOn": [
                "[variables('updateOldAssignment')]"
            ],
            "name": "[parameters('policyAssignmentId')]",
            "apiVersion": "2022-06-01",
            "location": "[deployment().location]",
            "identity": {
                "type": "SystemAssigned"
            },
            "properties": {
                "displayName": "[parameters('policyAssignmentDisplayName')]",
                "scope": "[variables('mgScope')]",
                "enforcementMode": "[parameters('enforcementMode')]",
                "description": "[parameters('description')]",
                "policyDefinitionId": "[extensionResourceId(variables('mgScope'), 'Microsoft.Authorization/policyDefinitions', parameters('policyDefinitionId'))]",
                "parameters": "[variables('policyParams')]",
                "resourceSelectors": "[variables('resourceSelectors')]"
            }
        },
        {
            "condition": "[not(empty(parameters('roleDefinitionIds')))]",
            "type": "Microsoft.Authorization/roleAssignments",
            "apiVersion": "2021-04-01-preview",
            "name": "[guid(parameters('policyAssignmentId'), parameters('targetManagementGroup'), parameters('roleDefinitionIds')[copyIndex()])]",
            "dependsOn": [
                "[parameters('policyAssignmentId')]"
            ],
            "properties": {
                "roleDefinitionId": "[parameters('roleDefinitionIds')[copyIndex()]]",
                "principalType": "ServicePrincipal",
                "principalId": "[toLower(reference(parameters('policyAssignmentId'), '2022-06-01', 'Full').identity.principalId)]"
            },
            "copy": {
                "name": "roleAssignmentsCopy",
                "count": "[length(parameters('roleDefinitionIds'))]",
                "mode": "serial",
                "batchSize": 1
            }
        }
    ]
}
asilverman commented 1 month ago

Actually, I figured out the issue is that for param I need to use @sys.description but it wasn't very straightforward to understand.

Maybe the message can be made more informative for these previously working decorators

jeskew commented 1 month ago

The error is being caused by this line in the template:

param description string = 'Default Description'

which adds a parameter that shadows the @description() decorator. It might be helpful to add a message like Did you mean 'sys.description'? to the BCP059 message (similar to how we suggest likely matches when an unrecognized property is referenced).

asilverman commented 1 month ago

Yeah I think it would be helpful to add a hint like that would help, especially since the file was generated by the decompiler. Maybe adding a conditional in the decompiler to disambiguate 'description' to 'sys.description' may be useful to avoid confusion