1.6.12.1. grept

在大规模管理 Terraform 模块时,我们常常需要确保每个模块仓库遵循统一的规范和最佳实践。Azure 开源的 grept 工具正是为此而生。grept(名称取义于 Go REPository linTer)是一个用于仓库治理的 Lint 工具,能够解析配置文件,根据预定义规则检查仓库内容,并自动给出修改计划或直接应用修改。下面我们将深入介绍 grept 的背景与用途、具体用法(结合实际示例),以及它在模块治理框架中的重要作用。

1.6.12.1.1. grept 的用途与背景

随着 Terraform 模块数量的增长,保持所有模块仓库的结构和内容一致性变得越来越困难。如果手动逐个仓库检查文件是否齐全(例如是否有正确的 LICENSE、README、CI 配置等),或督促维护者自己更新模板文件,这不仅费时费力,而且容易有疏漏。grept 的出现正是为了解决这些痛点。

grept 的设计初衷是通过自动化手段确保代码仓库遵循既定标准。它受到 RepoLinter 等工具的启发,提供了高度可扩展的仓库 lint 能力。对于 Azure Terraform 模块的治理来说,grept 提供了一个集中定义规则的途径:我们可以在一个中央配置中规定所有模块仓库必须具备哪些文件、内容和设置,由 grept 执行批量检查。这种方式极大地减轻了维护人员的负担,也保证了模块仓库的一致性和代码库质量。

在 Azure Verified Modules(AVM)项目中,grept 已经成为模块治理流程的关键环节之一。Azure 团队将所有校验规则集中存放在 Azure-Verified-Modules-Grept 仓库中,由 grept 使用这些配置对各个模块仓库进行扫描。举例来说,AVM 定义了资源模块仓库中需要与模板仓库保持同步的文件,如 LICENSE、代码规范文件、CI 工作流等。借助 grept,这些文件的存在和内容都能被自动验证,一旦某个模块仓库缺少这些文件或版本落后,grept 就会将其标记出来并提出修复方案。

总的来说,grept 使我们能够以标准化、可重复的方式治理数十上百个 Terraform 模块仓库,解决了人工检查易出错、难以规模化的问题。它确保每个仓库都满足预先定义的合规要求,从而维护了仓库间的一致性和高质量。

1.6.12.1.2. 如何使用 grept

从用户角度来看,grept 的使用过程类似于 Terraform 等基础设施即代码工具的“计划(plan)”和“应用(apply)”阶段。我们可以通过编写 HCL 配置文件(扩展名为 .grept.hcl)来定义仓库治理的规则,然后运行 grept 检查并应用这些规则。下面我们分几个方面介绍其使用方法:

1. 准备 grept 配置文件grept 的配置采用 HCL 语法,包含三个主要构件:data(数据)、rule(规则)和 fix(修复)。

  • data 数据块用于收集所需的信息,可以是从本地仓库读取文件/目录信息,也可以通过网络获取数据。例如,可以使用内置的 http 数据源从远程URL获取文件内容,或使用 github_repository_teams 数据源获取某个GitHub仓库的团队列表。
  • rule 规则块定义具体的检查条件,每条规则对应一个需要满足的约束。grept 内置了多种规则类型,如 dir_exist(检查目录是否存在)、file_hash(检查文件哈希是否匹配)以及 must_be_true(自定义检查规则)等。规则的典型用法是引用 data 收集的数据来做判断。
  • fix 修复块则定义当规则不满足时要采取的行动。fix 会引用对应的 rule(通过 rule_ids 关联)并在规则失败时执行。例如,有 local_file 类型的 fix 可以将本地缺失或不合规的文件内容替换,git_ignore fix 可以自动向 .gitignore 中添加指定条目。通过 rulefix 的配合,grept 不仅能“发现”问题,还能“一键修复”问题。

2. 编写规则示例:下面通过一个具体示例来说明配置的编写方式。假设我们希望确保每个模块仓库都包含标准的 MIT LICENSE 文件,并且内容与官方模板一致。我们可以编写如下的 grept 配置(摘自 Azure Verified Modules 的实际规则配置):

data "http" "mit_license" {
  url = "https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-template/main/LICENSE"
}

rule "file_hash" "license" {
  glob = "LICENSE"
  hash = sha1(data.http.mit_license.response_body)
}

fix "local_file" "license" {
  rule_ids = [rule.file_hash.license.id]
  paths    = [rule.file_hash.license.glob]
  content  = data.http.mit_license.response_body
}

上述配置由三个部分组成:data.http.mit_license 从远程获取了标准 MIT 许可证文本;rule.file_hash.license 则计算当前仓库中 LICENSE 文件的哈希并与模板内容的哈希进行比较;fix.local_file.license 则指定如果规则不通过(意味着当前仓库 LICENSE 不存在或内容不符),就将模板文本写入到本地 LICENSE 文件中。通过这几个区块,grept 能够自动检查并修复许可证文件,使其与标准模板同步。

类似的,我们可以为其他需要强制的规范编写规则,例如:验证 .gitignore 文件包含必要的忽略项,如果缺失则自动添加;检查仓库是否存在CI/CD工作流配置文件,并确保其内容与最新模板一致;检查是否存在代码所有者 (CODEOWNERS) 文件并包含正确的团队/人员等。grept 提供的丰富数据源和修复动作使这些检查变得相对简单。例如,可以使用 data.git_ignore 来获取当前 .gitignore 列表,并结合 fix.git_ignore 来自动追加条目。所有这些逻辑都可以用声明式的 HCL 来表达,我们只需提前编写好规则配置。

3. 运行 grept plan(计划):编写好配置后,就可以使用 grept 对目标仓库进行检查。在命令行中执行 grept plan [配置文件夹路径]grept 会加载该目录下所有以 .grept.hcl 结尾的配置文件,然后根据规则对当前仓库进行分析。需要注意,[配置文件夹路径] 可以是本地路径,也可以是一个远程引用(例如 Git 仓库地址),grept 支持通过 HashiCorp go-getter 语法加载远程配置。这意味着,我们可以将治理规则集中托管在某个仓库,使用时直接引用该远程路径即可,这正是 Azure Verified Modules 的做法。

执行 grept plan 后,工具会输出一个“计划”。如果所有规则检查都通过,那么 grept 会反馈类似“所有规则检查成功,无需更改”(对应英文输出 “All rule checks successful, nothing to do.”)。而如果有任何规则未满足,grept 会列出需要进行的更改计划。例如,它可能指出某些文件缺失或内容不匹配,以及将如何修复这些问题。这个计划让我们在实际修改之前清楚了解哪些地方不符合规范。

4. 运行 grept apply(应用):在确认计划后,我们可以执行 grept apply [配置文件夹路径] 将修改付诸实现。apply 命令会按照先前 plan 阶段生成的计划,对仓库进行必要的改动。默认情况下,grept 在应用每项修复前可能会要求确认,但我们可以使用 -a--auto 参数开启自动批准,以便批量无侵入地应用所有修复。当所有修复成功完成后,grept 将输出类似“计划已成功应用”(对应英文输出 “Plan applied successfully.”)的信息。经过这一步,仓库就会自动变更为符合所有规则的状态。

1.6.12.1.3. 结合示例的实战分析

为了更直观地理解 grept 的用法,我们结合实际场景来分析 grept 在 Azure Terraform 模块治理中的工作流程。Azure Verified Modules 项目使用一个中心化的 grept 配置(存放在 Azure-Verified-Modules-Grept 仓库中)来治理众多模块仓库。现在让我们以“模板文件同步”为例,看看 grept 如何帮助我们实战解决问题。

场景:模板文件同步。Azure 官方提供了一个 Terraform 模块模板仓库(terraform-azurerm-avm-template),其中包含模块仓库应有的标准文件和配置。随着时间推移,模板可能更新(例如新增了更完善的 GitHub Actions 工作流文件或调整了 README 格式),而已有的模块仓库可能没有及时跟进这些变化。传统做法下,我们需要人工通知各模块维护者更新仓库文件,或发送PR逐个修改,这在大规模情况下非常低效。而借助 grept,这一过程可以大幅简化。

grept 检查:在中心配置中,我们为所有关键的模板文件都编写了相应的规则。例如前文展示的 LICENSE 规则就是其中之一。再比如,我们有规则确保每个仓库都有最新的GitHub Actions工作流配置文件(如 .github/workflows/ci.yaml)。实现方法与LICENSE类似:通过 data "http" 下载模板仓库中对应文件的内容,使用 rule "file_hash" 对比目标仓库文件的哈希值,如果不匹配则触发 fix "local_file" 来更新文件内容。对于需要全新添加的文件,规则可以设计为检查文件不存在就视为不通过,然后 fix 执行添加。

AVM 目前已经将 grept 整合进了 CI 流水线以及 Pre-Commit 中。针对每次提交的 Pull Request,流水线都会运行 grept,随后检查 git status 是否返回有文件发生变更,假设有,则阻止流水线运行。对于每个差异,grept 已经根据配置确定了修复步骤,因此用户只需要在提交前运行 make pre-commit 即可修复。

1.6.12.1.4. grept 样例代码

类似 Terraform,grept 也支持 for_each 实现多实例声明,例如下面的例子:

locals {
  synced_files = toset([
    "_footer.md",
    ".github/CODEOWNERS",
    ".github/ISSUE_TEMPLATE/avm_module_issue.yml",
    ".github/ISSUE_TEMPLATE/avm_question_feedback.yml",
    ".github/ISSUE_TEMPLATE/config.yml",
    ".github/PULL_REQUEST_TEMPLATE.md",
    ".github/policies/avmrequiredfiles.yml",
    ".github/policies/eventResponder.yml",
    ".github/policies/scheduledSearches.yml",
    ".github/workflows/e2e.yml",
    ".github/workflows/linting.yml",
    ".github/workflows/version-check.yml",
    ".terraform-docs.yml",
    "avm.bat",
    "CODE_OF_CONDUCT.md",
    "CONTRIBUTING.md",
    "examples/.terraform-docs.yml",
    "LICENSE",
    "Makefile",
    "SECURITY.md",
    ".editorconfig",
  ])
}

data "http" "synced_files" {
  for_each = local.synced_files

  request_headers = merge({}, local.common_http_headers)
  url             = "${local.url_prefix}/${each.value}"
}

rule "file_hash" "synced_files" {
  for_each = local.synced_files

  glob = each.value
  hash = sha1(data.http.synced_files[each.value].response_body)
}

fix "local_file" "synced_files" {
  for_each = local.synced_files

  rule_ids = [rule.file_hash.synced_files[each.value].id]
  paths    = [each.value]
  content  = data.http.synced_files[each.value].response_body
}

通过 for_each,我们可以方便地复用代码块,对 local.synced_files 中列出的每一个文件执行检查和同步。grept 有意兼容了 Terraform 的大部分语法,你可以使用几乎所有 Terraform 内置的函数,也可以使用 localsvariable 块等等。

类似于 Terraform 的 console 命令grept 也提供了 console 命令,便于用户对代码中的各种表达式进行调试。

1.6.12.1.5. grept 在治理框架中的意义

grept 引入 Terraform 模块治理框架,带来了质的提升。其意义主要体现在以下几个方面:

  • 标准化与一致性:借助 grept,我们可以在整个组织范围内推行统一的仓库规范。所有模块仓库都通过同一套 grept 规则来约束,这意味着无论是谁创建或维护模块,都需要满足相同的要求。标准不再停留在文档上,而是落实为自动化的检查和修复措施。这种标准化极大提高了一致性,模块消费者也能因为模块结构和流程统一而受益。

  • 自动验证与纠偏grept 将治理要求转化为代码后,能自动持续地验证仓库是否符合要求,并在偏离时立即提出纠正方案。相比人工定期审计仓库,grept 的及时性准确性更高。而且由于有自动修复能力,许多问题在无人干预的情况下就被纠正了。正如 AVM 项目的实践,grept 的中央工作流按照计划扫描并修复仓库,使得“漂移”得到持续治理。

  • 规模化管理:对于少量模块也许人工管理尚可,但对于数十上百的模块仓库,没有自动化工具的帮助几乎无法想象。grept 通过一次编写配置,支持对任意数量仓库执行相同的检查操作。这使得模块数量增长时,治理工作量并不会线性增长——新增模块只要套用已有规则即可。这种可扩展性非常关键,让治理框架可以随着模块库规模扩大而从容应对。

  • 降低维护成本:使用 grept 后,模块维护者的负担实际上有所减轻。因为许多仓库层面的要求由自动工具把关并初步修复,维护者无需逐项记忆所有规范细节。一旦有不合规之处,他们会收到 grept 发起的 PR 提醒;他们要做的只是审核合并。这相当于将“发现问题->解决问题”的流程自动化了,大大减少了人为沟通和往返的成本。另外,这也使得 AVM 中央管理团队可以快捷地新增和维护所有 AVM Terraform 模块都必须拥有的 Terraform 配置代码,例如,为所有模块添加 Telemetry 收集代码。

  • 治理规则的集中管理和演进grept 配置集中存放并版本化管理,这使我们可以方便地更新治理规则并追踪变化。当有新的规范(例如安全合规要求或公司政策改变)需要引入时,只需更新中央 grept 配置,然后让工具跑一遍,所有模块即可获得更新。提到 AVM 正在为仓库设置等推出新的 grept 规则,也是通过这种集中修改配置来逐步实施的。这种模式保障了治理框架的灵活性持续改进能力。

  • 与 Terraform 高度兼容的语法grept 被刻意设计成与 Terraform 语法高度兼容,这样一个熟练使用 Terraform 的技术人员可以非常轻松地掌握 grept 策略的书写方法。

综上,grept 在大规模 Terraform 模块治理中扮演了自动监督者和修复者的角色。无论是同步文件内容还是调整仓库设置,只要我们预先制定好标准并编写规则,grept 就能反复地、持续地为我们执行这些检查和修改。在大规模模块治理的实战中,它显著降低了人力开销,并避免了因人工操作不一致带来的疏漏。。它将过去繁琐的人工作业转变为自动化流程,使治理工作更加精确、高效且可持续。Azure Verified Modules 的成功经验表明,通过 grept 实现的仓库治理,可以让我们对模块库的质量和规范有更高的信心。当模块仓库在结构和流程上都保持一致并不断符合最新标准时,整个模块生态的可靠性和可维护性也随之提升。grept 所体现的“治理即代码”理念,无疑将成为未来大规模基础设施模块管理的一个重要趋势和实践方向。

results matching ""

    No results matching ""