如何通过本地JSON文件在Terraform中创建Azure PolicyDefinition并解决报错?
问题场景
使用以下Terraform代码创建Azure策略定义:
resource "azurerm_policy_definition" "name-caf-alz-policy-sandbox-denyvnetpeering" { name = "Audit-AzureHybridBenefit" display_name = "未使用资源导致成本浪费应避免" description = "通过启用Azure混合权益优化成本。使用此策略定义作为成本控制手段,识别未使用AHUB的虚拟机。" mode = "All" policy_type = "Custom" policy_rule = file("${path.module}/lib/policy_definitions/policy_definition_es_deny_vnet_peer_cross_sub.json") management_group_id = data.azurerm_management_group.namemgmtgroup.id }
在Azure DevOps中执行时,terraform validate、init、plan步骤均正常,但执行terraform apply时抛出错误:
creating/updating Policy Definition "Audit-AzureHybridBenefit": policy.DefinitionsClient#CreateOrUpdateAtManagementGroup: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="InvalidPolicyRule" Message="Failed to parse policy rule: 'Could not find member 'apiVersion' on object of type 'PolicyRuleDefinition'. Path 'apiVersion'.'."
将policy_rule修改为jsonencode(file("${path.module}/lib/policy_definitions/policy_definition_es_deny_vnet_peer_cross_sub.json"))后,又出现新错误:
Error: expanding JSON for policy_rule: JSON: cannot unmarshal string into Go value of type map[string]interface {}
注:该JSON文件通过Azure GUI创建策略定义时可正常使用。
错误原因
- 第一个错误:使用的JSON文件是完整的Azure策略定义结构(包含
apiVersion、properties等外层字段),但Terraform的azurerm_policy_definition资源的policy_rule参数只需要策略规则本身(即原JSON中properties.policyRule对应的内容)。Azure API收到包含apiVersion的规则内容时,会判定为无效格式。 - 第二个错误:
file()函数已经将JSON文件读取为字符串,再用jsonencode()会对字符串进行二次编码,导致结果是一个JSON字符串,而Terraform期望policy_rule是map[string]interface{}类型,因此无法反序列化。
解决方案
方案1:通过Terraform代码提取规则内容
使用jsondecode()解析JSON文件,直接提取其中的policyRule部分:
resource "azurerm_policy_definition" "name-caf-alz-policy-sandbox-denyvnetpeering" { name = "Audit-AzureHybridBenefit" display_name = "未使用资源导致成本浪费应避免" description = "通过启用Azure混合权益优化成本。使用此策略定义作为成本控制手段,识别未使用AHUB的虚拟机。" mode = "All" policy_type = "Custom" policy_rule = jsondecode(file("${path.module}/lib/policy_definitions/policy_definition_es_deny_vnet_peer_cross_sub.json")).properties.policyRule management_group_id = data.azurerm_management_group.namemgmtgroup.id }
(注意:原JSON的结构是外层properties下包含policyRule,所以需要.properties.policyRule来定位)
方案2:修改JSON文件,仅保留规则内容
编辑JSON文件,删除外层的apiVersion、name、properties等字段,只保留policyRule对应的JSON对象。修改后的JSON示例:
{ "if": { "allOf": [ { "field": "type", "equals": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings" }, { "not": { "field": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings/remoteVirtualNetwork.id", "contains": "[subscription().id]" } } ] }, "then": { "effect": "deny" } }
之后Terraform代码可以继续使用file()函数直接读取:
policy_rule = file("${path.module}/lib/policy_definitions/policy_definition_es_deny_vnet_peer_cross_sub.json")
内容的提问来源于stack exchange,提问作者Xyltic

