You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 18:23:20