使用 Python 更新地形 (.tf) 文件

Updating terraform (.tf) files using Python

我正在尝试使用 python 更新 terraform 模板,尽管我在尝试查找模块时遇到了一些问题(例如 PyYAML 更新 yml 文件)。

我的目标是更新 git 存储库中的 terraform 文件,并创建一个拉取请求,合并后会触发一个管道来实施更改。

.tf 文件示例:

variable "variable_1" {}
variable "variable_2" {}

locals {
  temp_locals = 0
}

resource "aws_iam_role" "MY_AWS_ACCOUNT" {
  name = "MY_AWS_ACCOUNT"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      },
      "Principal": {
        "AWS": [
          "arn:aws:iam::<AWS_ACCOUNT_ID>:user/user.name1"
        ]
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "aws_iam_role_policy_attachment" {
}

想要在此文件中添加 user.name2 的 arn。

目前没有 Python Terraform 的底层语法 HCL 的实现,它可以支持对现有文件进行手术修改,因为这需要保留典型 HCL 解析器不会保留的信息,例如注释和顺序块中的参数。

另一种选择是使用 Terraform 的一个较少使用的功能,称为 Override Files,它允许您编写一个新文件来覆盖现有文件的一部分。这种机制专门针对这种情况,您希望使用人类编写的文件中的内容,但用机器生成的内容覆盖其中的一部分。

如果您将给定的示例代码作为 example.tf,那么您可以在它旁边的磁盘上放置另一个名为 example_override.tf.json 的文件,其内容如下:

{
  "resource": {
    "aws_iam_role": {
      "MY_AWS_ACCOUNT": {
        "assume_role_policy": "{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t  {\n\t\t\"Action\": \"sts:AssumeRole\",\n\t\t\"Condition\": {\n\t\t  \"Bool\": {\n\t\t\t\"aws:MultiFactorAuthPresent\": \"true\"\n\t\t  }\n\t\t},\n\t\t\"Principal\": {\n\t\t  \"AWS\": [\n\t\t\t\"arn:aws:iam::<AWS_ACCOUNT_ID>:user/user.name2\"\n\t\t  ]\n\t\t},\n\t\t\"Effect\": \"Allow\",\n\t\t\"Sid\": \"\"\n\t  }\n\t]\n  }"
      }
    }
  }
}

因为这个附加文件使用 Terraform's alternative JSON syntax, it is easy to generate from any language that has a JSON serializer available, including Python. Terraform itself will read both files and take the assume_role_policy from the override file as overriding the corresponding one in the base file, as described in Merging Behavior。然后,您可以不修改原始 HCL 文件,避免干扰该文件中以人为本的格式设置决策的问题。


HCL 的 Go 实现是规范的,也是 Terraform 本身使用的,它有一个包 hclwrite 这是一个专门的 API 用于直接修改现有的 HCL 源代码,同时保留所有未修改的令牌及其顺序。因此,用 Go 编写的程序可能会使用该包实现您正在寻找的结果:

package main

import (
    "fmt"
    "log"

    hcl "github.com/hashicorp/hcl/v2"
    "github.com/hashicorp/hcl/v2/hclwrite"
    "github.com/zclconf/go-cty/cty"
)

func main() {
    // In a real program, use ioutil.ReadFile to read a file from disk.
    // But for this example, I will use a const string.
    src := []byte(oldSource)
    f, diags := hclwrite.ParseConfig(src, "example.tf", hcl.Pos{Line: 1, Column: 1})
    if diags.HasErrors() {
        log.Fatal(diags)
    }

    for _, block := range f.Body().Blocks() {
        if block.Type() != "resource" {
            continue
        }
        if labels := block.Labels(); len(labels) < 2 || labels[0] != "aws_iam_role" || labels[1] != "MY_AWS_ACCOUNT" {
            continue
        }

        block.Body().SetAttributeValue("assume_role_policy", cty.StringVal(newPolicy))
    }

    // In a real program, maybe write these bytes to a file on disk.
    fmt.Println(string(f.Bytes()))
}

const oldSource = `
variable "variable_1" {}
variable "variable_2" {}

locals {
  temp_locals = 0
}

resource "aws_iam_role" "MY_AWS_ACCOUNT" {
  name = "MY_AWS_ACCOUNT"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      },
      "Principal": {
        "AWS": [
          "arn:aws:iam::<AWS_ACCOUNT_ID>:user/user.name1"
        ]
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "aws_iam_role_policy_attachment" {
}
`

const newPolicy = `
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": "sts:AssumeRole",
        "Condition": {
          "Bool": {
            "aws:MultiFactorAuthPresent": "true"
          }
        },
        "Principal": {
          "AWS": [
            "arn:aws:iam::<AWS_ACCOUNT_ID>:user/user.name2"
          ]
        },
        "Effect": "Allow",
        "Sid": ""
      }
    ]
  }
`

但是,此程序的输出确实展示了自动生成主要设计用于人类编写和阅读的语言的局限性之一:

variable "variable_1" {}
variable "variable_2" {}

locals {
  temp_locals = 0
}

resource "aws_iam_role" "MY_AWS_ACCOUNT" {
  name = "MY_AWS_ACCOUNT"

  assume_role_policy = "\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t  {\n\t\t\"Action\": \"sts:AssumeRole\",\n\t\t\"Condition\": {\n\t\t  \"Bool\": {\n\t\t\t\"aws:MultiFactorAuthPresent\": \"true\"\n\t\t  }\n\t\t},\n\t\t\"Principal\": {\n\t\t  \"AWS\": [\n\t\t\t\"arn:aws:iam::<AWS_ACCOUNT_ID>:user/user.name2\"\n\t\t  ]\n\t\t},\n\t\t\"Effect\": \"Allow\",\n\t\t\"Sid\": \"\"\n\t  }\n\t]\n  }\n"
}

resource "aws_iam_role_policy_attachment" "aws_iam_role_policy_attachment" {
}

作为人类,我们可以就如何格式化事物以提高可读性做出主观决定,但是 hclwrite 刚刚默认为此字符串使用引号字符串形式,在这种情况下这是一个糟糕的选择人类作者可能不会做出。