如何用Golang脚本修改Terraform HCL文件中的Azure NSG源IP?
问题原因分析
你遇到的错误核心是Terraform语法混淆:azurerm_network_security_group中的security_rule是嵌套块,而非属性参数,但你的Golang代码在解析/生成HCL时,错误地将其处理成了参数类型,触发Terraform的语法校验错误。
解决方案
方案1:修正Golang结构体定义(适配hclsimple/gohcl)
如果坚持用结构体映射方式解析HCL,必须给security_rule字段添加block标签,明确标记它是嵌套块类型:
package main import ( "context" "fmt" "net/http" "os" "strings" "github.com/hashicorp/hcl/v2/hclsimple" ) // 定义SecurityRule结构体,对应Terraform的security_rule嵌套块 type SecurityRule struct { Name string `hcl:"name"` Priority int `hcl:"priority"` Direction string `hcl:"direction"` Access string `hcl:"access"` Protocol string `hcl:"protocol"` SourcePortRange string `hcl:"source_port_range"` DestinationPortRange string `hcl:"destination_port_range"` SourceAddressPrefixes []string `hcl:"source_address_prefixes"` DestinationAddressPrefixes []string `hcl:"destination_address_prefixes"` } // 定义NetworkSecurityGroup结构体,注意security_rule字段的block标签 type NetworkSecurityGroup struct { ResourceType string `hcl:"azurerm_network_security_group,block"` Name string `hcl:"name"` Location string `hcl:"location"` ResourceGroupName string `hcl:"resource_group_name"` SecurityRule []SecurityRule `hcl:"security_rule,block"` // 关键:添加,block标记 } // 定义根结构体,用于解析整个tf文件 type TFConfig struct { NSGs []NetworkSecurityGroup `hcl:"resource,block"` } func getPublicIP() (string, error) { resp, err := http.Get("https://icanhazip.com") if err != nil { return "", err } defer resp.Body.Close() ipBytes := make([]byte, 100) n, err := resp.Body.Read(ipBytes) if err != nil { return "", err } ip := strings.TrimSpace(string(ipBytes[:n])) return ip + "/32", nil } func main() { ip, err := getPublicIP() if err != nil { fmt.Printf("获取公网IP失败: %v\n", err) os.Exit(1) } var config TFConfig err = hclsimple.DecodeFile("nsg.tf", nil, &config) if err != nil { fmt.Printf("解析HCL失败: %v\n", err) os.Exit(1) } // 遍历所有NSG规则,修改目标规则的source_address_prefixes for i := range config.NSGs { for j := range config.NSGs[i].SecurityRule { rule := &config.NSGs[i].SecurityRule[j] // 匹配SSH(22)、HTTP(80)、HTTPS(443)规则 if rule.DestinationPortRange == "22" || rule.DestinationPortRange == "80" || rule.DestinationPortRange == "443" { rule.SourceAddressPrefixes = []string{ip} } } } // 将修改后的结构体写回文件(需要用gohcl生成HCL) file := hclwrite.NewEmptyFile() gohcl.EncodeIntoBody(&config, file.Body()) err = os.WriteFile("nsg.tf", file.Bytes(), 0644) if err != nil { fmt.Printf("写入文件失败: %v\n", err) os.Exit(1) } fmt.Println("NSG规则已成功更新") }
方案2:用hclwrite直接操作HCL AST(更灵活)
如果不需要严格映射结构体,直接操作HCL抽象语法树的方式更适合修改场景,避免结构体匹配错误:
package main import ( "fmt" "io/ioutil" "net/http" "os" "strings" "github.com/hashicorp/hcl/v2/hclwrite" ) func getPublicIP() (string, error) { resp, err := http.Get("https://icanhazip.com") if err != nil { return "", err } defer resp.Body.Close() ipBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } ip := strings.TrimSpace(string(ipBytes)) return ip + "/32", nil } func main() { ip, err := getPublicIP() if err != nil { fmt.Printf("获取公网IP失败: %v\n", err) os.Exit(1) } // 读取NSG配置文件 f, err := os.Open("nsg.tf") if err != nil { fmt.Printf("打开文件失败: %v\n", err) os.Exit(1) } defer f.Close() // 解析HCL为AST file, err := hclwrite.ParseConfigFromReader(f) if err != nil { fmt.Printf("解析HCL失败: %v\n", err) os.Exit(1) } // 遍历所有块,找到azurerm_network_security_group for _, block := range file.Body().Blocks() { if block.Type() == "azurerm_network_security_group" { // 遍历嵌套的security_rule块 for _, srBlock := range block.Body().Blocks() { if srBlock.Type() == "security_rule" { // 获取目标端口属性 destPortAttr := srBlock.Body().GetAttribute("destination_port_range") if destPortAttr != nil { portStr := strings.Trim(string(destPortAttr.Expr().BuildTokens(nil).Bytes()), `"`) // 匹配SSH、网站端口 if portStr == "22" || portStr == "80" || portStr == "443" { // 更新source_address_prefixes srBlock.Body().SetAttributeValue("source_address_prefixes", hclwrite.TokensForValue([]string{ip})) } } } } } } // 写回文件 err = ioutil.WriteFile("nsg.tf", file.Bytes(), 0644) if err != nil { fmt.Printf("写入文件失败: %v\n", err) os.Exit(1) } fmt.Println("NSG规则已成功更新") }
额外检查
确保你的nsg.tf文件本身语法正确,security_rule是嵌套块而非参数形式,正确示例:
resource "azurerm_network_security_group" "example" { name = "example-nsg" location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name # 正确:security_rule是嵌套块 security_rule { name = "allow-ssh" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefixes = ["0.0.0.0/0"] destination_address_prefixes = ["*"] } }
内容的提问来源于stack exchange,提问作者kafka
相关产品推荐
相关产品推荐

