Terraform 系列 - 使用 Dynamic Blocks 对 Blocks 进行迭代

本文最后更新于:2023年10月31日 下午

系列文章

概述

Terraform 系列文章 介绍了使用 Grafana Terraform Provider, 基于 Terraform 的 IaC 方法论, 来批量自动化创建 Grafana 的各类资源, 包括 Dashboard/Datasource 等.

现在有这么一个现实需求:

出于权限控制的需求, 需要启用 Folder Permissions, 限制指定的某几个 team 可以有该 Folder 的 view 权限.

该如何实现?🤔

解决方案

通过 Terraform 的 for_eachdynamic blocks 实现.

基本概念

Dynamic Blocks

在资源 (resource) 等顶级块结构中,表达式通常只能在使用 name = expression 形式为参数赋值时使用。这涵盖了许多用途,但有些资源类型的参数中包含可重复嵌套的块(block),这些块通常代表与包含对象相关(或嵌入其中)的独立对象:

1
2
3
4
5
6
7
8
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name" # can use expressions here

setting {
# but the "setting" block is always a literal block
}
}

您可以使用特殊的 dynamic 块类型动态构建可重复嵌套的块,如resourcedataproviderprovisioner 块都支持这种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name"
application = "${aws_elastic_beanstalk_application.tftest.name}"
solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"

dynamic "setting" {
for_each = var.settings
content {
namespace = setting.value["namespace"]
name = setting.value["name"]
value = setting.value["value"]
}
}
}

动态块的作用与 for 表达式 很相似,但它产生的是嵌套代码块,而不是复数值。它遍历给定的复数值,并为复数值的每个元素生成一个嵌套块。

  • 动态代码块的标签(上例中的 "setting")指定了要生成的嵌套代码块的类型。
  • for_each " 参数提供了要遍历的复合值。
  • iterator 参数(可选)设置了一个临时变量的名称,该变量代表复数值的当前元素。如果省略,变量名默认为 dynamic 块(上例中为 "setting")的标签。
  • labels 参数(可选)是一个字符串列表,它按顺序指定了要用于每个生成块的块标签。你可以在此值中使用临时迭代变量。
  • 嵌套的 content 块定义了每个生成块的主体。你可以在此块中使用临时迭代变量。

由于 for_each 参数可接受任何集合或结构值,因此可以使用 for 表达式或 splat 表达式来转换现有集合。

迭代器对象(上例中的 setting)有两个属性:

  • key 是当前元素的映射键或列表元素索引。如果 for_each 表达式产生了一个 set 值,则 keyvalue 相同。
  • value 是当前元素的值。

dynamic 代码块只能生成属于正在配置的 resource 类型、data 源、provider 或 provisioner 的参数。不能 生成 meta-argument 块,如 lifecycleprovisioner块,因为 Terraform 必须先处理这些参数块,然后才能安全地评估表达式。

for_each 值必须是一个集合,每个所需的嵌套块包含一个元素。如果需要根据嵌套数据结构或多个数据结构的元素组合声明资源实例,可以使用 Terraform 表达式和函数推导出合适的值。有关此类情况的一些常见示例,请参阅 flattensetproduct函数。

有些提供程序定义的资源类型包括相互嵌套的多层区块。您可以在必要时动态生成这些嵌套结构,方法是将 dynamic 模块嵌套在其他 dynamic 模块的 content 部分中。

例如,一个模块可能会接受如下复杂的数据结构:

1
2
3
4
5
6
7
variable "load_balancer_origin_groups" {
type = map(object({
origins = set(object({
hostname = string
}))
}))
}

如果要定义一个 resource,其类型需要为每个 origin group 创建一个块,然后为组内的每个 origin 创建嵌套块,则可以要求 Terraform 使用以下嵌套的 dynamic 块动态生成该资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
dynamic "origin_group" {
for_each = var.load_balancer_origin_groups
content {
name = origin_group.key

dynamic "origin" {
for_each = origin_group.value.origins
content {
hostname = origin.value.hostname
}
}
}
}

在使用嵌套的 dynamic 代码块时,尤其要注意每个代码块的迭代符号。在上例中,origin_group.value 指向外层代码块的当前元素,而 origin.value 指向内层代码块的当前元素。

如果一个特定的资源类型定义了嵌套块,而这些嵌套块的类型名称与其父类中的一个类型名称相同,则可以在每个 dynamic 块中使用 iterator 参数来选择一个不同的迭代器符号,使两者更容易区分。

过度使用 dynamic 块会使配置变得难以阅读和维护,因此我们建议仅在需要隐藏细节以便为可重用模块构建简洁的用户界面时使用它们。在可能的情况下,一定要按字面意思写出嵌套模块。

实战

需求:

出于权限控制的需求, 需要启用 Folder Permissions, 限制指定的某几个 team 可以有该 Folder 的 view 权限.

对应的 Terraform 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
locals {
teams = {
"dev",
"busi",
"ops",
"data",
"pm"
}
}

resource "grafana_folder_permission" "foldersPermission" {

folder_uid = "demo"

dynamic "permissions" {
for_each = local.teams
content {
team_id = grafana_team.teams[each.key].id
permission = "View"
}
}
}

说明:

  • permissions (Block Set, Min: 1) 要添加 / 更新的权限项目。列表中没有的项目将被删除。

完成🎉🎉🎉

📚️参考文档


Terraform 系列 - 使用 Dynamic Blocks 对 Blocks 进行迭代
https://ewhisper.cn/posts/2663/
作者
东风微鸣
发布于
2023年10月27日
许可协议