Terraform 系列-使用 for-each 对本地 json 进行迭代
系列文章
概述
前文 Grafana 系列 - Grafana Terraform Provider 基础 介绍了使用 Grafana Terraform Provider 创建 Datasource.
现在有这么一个现实需求:
有大量的同类型 (type) 的 datasource 需要批量添加,而且这些 datasource 的基本信息是以 json 的格式已经存在。
需要对 json 进行解析/精简/重构等操作并将 json 作为 Terraform 的 datasource.
Json 的格式可能类似于这样:
[
{
"env_name": "dev",
"prom_url": "http://dev-prom.example.com",
"es_url": "http://dev-es.example.com:9200",
"jaeger_url": "http://dev-jaeger.example.com"
},
{
"env_name": "test",
"prom_url": "http://test-prom.example.com",
"es_url": "http://test-es.example.com:9200",
"jaeger_url": "http://test-jaeger.example.com"
}
]
Notes:
举一反三,后面的解决方案也适用于其他任意 Json 格式。
该如何实现?
解决方案
通过 Terraform 的 locals
jsondecode
for
循环 和 for_each
实现。
具体如下:
- 构造一个 local 变量
- local 变量从 .json 文件中读取并内容并通过
jsondecode
+file
将 json 文件解码为 object - 使用
for
循环,将 object 根据当前需求调整,将例子中env_name
作为 key, 将其他作为 value - 批量创建资源时,通过
for_each
, 进行批量创建。
基本概念
locals
locals
为 表达式 指定一个名称,所以你可以在一个模块中多次使用这个名称,而不用重复表达式。
如果你熟悉传统的编程语言,把 Terraform 模块比作函数定义可能会很有用:
- variables(输入变量) 就像函数的参数。
- outputs(输出值) 就像函数的返回值。
locals
就像一个函数的临时本地变量(局部值)。
一旦声明了一个本地值,你可以在 表达式 中以local.<NAME>
的形式引用它。
本地值有助于避免在配置中多次重复相同的值或表达式,只有在一个单一的值或结果被用于许多地方的情况下,才可以适度地使用本地值。能够在一个中心位置轻松地改变数值是本地值的关键优势。
file
函数
file
读取指定路径下的文件内容,并将其作为 string 返回。
> file("${path.module}/hello.txt")
Hello World
jsondecode
函数
jsondecode
将一个给定的 string 解释为 JSON,返回该字符串的解码结果。
该函数以如下方式将 JSON 值映射到 Terraform 语言 type:
JSON type | Terraform type |
---|---|
String | string |
Number | number |
Boolean | bool |
Object | object(...) 的属性类型根据此表确定 |
Array | tuple(...) 的元素类型根据此表确定 |
Null | Terraform 语言的 null 值 |
Terraform 语言的自动类型转换规则意味着你通常不需要担心一个给定的值到底会产生什么类型,只需以直观的方式使用结果即可。
> jsondecode("{\"hello\": \"world\"}")
{
"hello" = "world"
}
> jsondecode("true")
true
jsonencode
执行相反的操作,将一个 string 编码为 JSON。
for
表达式
一个for
表达式通过转换另一个复杂类型的值来创建一个复杂类型的值。输入值中的每个元素可以对应于结果中的一个或零个值,并且可以使用一个任意的表达式来将每个输入元素转化为输出元素。
例如,如果var.list
是一个字符串的列表,那么下面的表达式将产生一个全大写字母的字符串的元组:
[for s in var.list : upper(s)]
这个for
表达式遍历了var.list
中的每个元素,然后评估表达式upper(s)
,将s
设置为每个相应的元素。然后它用所有执行该表达式的结果按相同的顺序建立一个新的元组值。
一个for
表达式的输入(在in
关键字之后给出)可以是一个列表,一个集合,一个元组,一个 map,或者一个对象 (object)。
上面的例子显示了一个只有一个临时符号s
的for
表达式,但是一个for
表达式可以选择声明一对临时符号,以便也使用每个项目的键或索引:
[for k, v in var.map : length(k) + length(v)]
对于 map 或对象类型,像上面那样,k
符号是指当前元素的键或属性名称。你也可以对列表和 map 使用双符号形式,在这种情况下,额外的符号是每个元素的索引,从 0 开始,常规的符号名称是i
或idx
,除非选择一个很有帮助的更具体的名称:
[for i, v in var.list : "${i} is ${v}"]
索引或关键符号总是可选的。如果你在for
关键字后面只指定一个符号,那么这个符号将总是代表输入集合的每个元素的值。
for
表达式周围的括号的类型决定了它产生的结果的类型。
上面的例子使用[
和]
,产生一个元组。如果你用{
和}
代替,结果是一个对象,你必须提供两个结果表达式,用=>
符号分开:
{for s in var.list : s => upper(s)}
这个表达式产生一个对象,其属性是来自var.list
的原始元素,其相应的值是大写版本。例如,产生的值可能如下:
{
foo = "FOO"
bar = "BAR"
baz = "BAZ"
}
单独的for
表达式只能产生一个对象值或一个元组值,但 Terraform 的自动类型转换规则意味着你通常可以在期望使用列表、map 和集合 (set) 的地方使用其结果。
一个 for
表达式也可以包括一个可选的 if
子句来过滤源集合中的元素,产生一个比源值更少元素的值:
[for s in var.list : upper(s) if s != ""]
在for
表达式中过滤集合的一个常见原因是根据一些标准将一个源集合分成两个独立的集合。例如,如果输入的var.users
是一个对象的映射,其中每个对象都有一个属性is_admin
,那么你可能希望产生包含管理员和非管理员对象的单独映射:
variable "users" {
type = map(object({
is_admin = bool
}))
}
locals {
admin_users = {
for name, user in var.users : name => user
if user.is_admin
}
regular_users = {
for name, user in var.users : name => user
if !user.is_admin
}
}
因为for
表达式可以从无序类型(map、对象、集合 set)转换为有序类型(列表、元祖),Terraform 必须为无序集合的元素选择一个隐含的排序。
对于 map 和对象,Terraform 通过键或属性名称对元素进行排序,使用词法排序。
对于字符串的集合,Terraform 按其值排序,使用词法排序。
for
表达式机制是为了在表达式中从其他集合值中构建集合值,然后你可以将其分配给期待复杂值的单个资源参数。
for_each
元参数
默认情况下,一个 资源块 配置一个真实的基础设施对象(同样,一个 模块块 将一个子模块的内容纳入一次配置)。然而,有时你想管理几个类似的对象(比如一个固定的计算实例池),而不需要为每个对象单独写一个块。Terraform 有两种方法可以做到这一点: count
和 for_each
。
如果一个资源或模块块包括一个for_each
参数,其值是一个 map 或字符串集合,Terraform 为该 map 或字符串集合的每个成员创建一个实例。
版本说明: for_each
是在 Terraform 0.12.6 中添加的。Terraform 0.13 中增加了对for_each
的模块支持;以前的版本只能在资源中使用它。
注意:一个特定的资源或模块块不能同时使用count
和for_each
。
for_each
是 Terraform 语言定义的一个元参数。它可以与模块和每一种资源类型一起使用。
for_each
元参数接受一个 map 或字符串集合,并为该 map 或字符串集合的每个项目创建一个实例。每个实例都有一个独特的基础设施对象与之相关联,每个实例都在应用配置时被单独创建、更新或销毁。
Map:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
字符串集合:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}
在设置了for_each
的区块中,表达式中还有一个each
对象,所以你可以修改每个实例的配置。这个对象有两个属性:
each.key
- 这个实例对应的 map 键(或集合成员)。each.value
- 该实例对应的 map 值。(如果提供了一个集合,这与each.key
相同。)
当 for_each
被设置时,Terraform 区分了区块本身和与之相关的多个资源或模块实例。实例由提供给for_each
的值中的一个 map 键(或集合成员)来识别。
<TYPE>.<NAME>
或module.<NAME>
(例如,azurerm_resource_group.rg
) 代表这个块。<TYPE>.<NAME>[<KEY>]
或module.<NAME>[<KEY>]
(例如,azurerm_resource_group.rg["a_group"]
,azurerm_resource_group.rg["another_group"]
, etc.) 代表独立的实例
这与没有count
或for_each
的资源和模块不同,它们可以在没有索引或键的情况下被引用。
String & Template
字符串是 Terraform 中最复杂的一种文字表达,也是最常用的一种。
Terraform 同时支持字符串的引号语法和 heredoc
语法。这两种语法都支持用于插值和操作文本的模板序列。
带引号的字符串是一系列由双引号字符("
)划定的字符。
有两个不使用反斜线的特殊转义序列:
Sequence | Replacement |
---|---|
$${ |
字面意思是${ ,不会开始一个插值序列。 |
%%{ |
字面意思是%{ ,不会开始一个模板指令序列。 |
${ ... }
序列是一个插值,它评估标记之间给出的表达式,如果有必要,将结果转换为字符串,然后将其插入到最终的字符串中:
"Hello, ${var.name}!"
在上面的例子中,命名的对象var.name
被访问,其值被插入到字符串中,产生的结果类似 "Hello, Juan!"。
%{ ... }
序列是一个指令,它允许有条件的结果和对集合的迭代,类似于条件和for
表达式。
以下指令被支持:
%{if <BOOL>}
/%{else}
/%{endif}
指令根据一个 bool 表达式的值在两个模板之间进行选择:"Hello, %{ if var.name != "" }${var.name}%{ else }unnamed%{ endif }!"
else
部分可以省略,在这种情况下,如果条件表达式返回false
,结果就是一个空字符串。%{for <NAME> in <COLLECTION>}
/%{endfor}
指令在给定的集合或结构值的元素上进行迭代,对每个元素评估一次给定的模板,将结果串联起来:<<EOT
%{ for ip in aws_instance.example.*.private_ip }
server ${ip}
%{ endfor }
EOT
实战
需求:
有大量的同类型 (type) 的 datasource 需要批量添加,而且这些 datasource 的基本信息是以 json 的格式已经存在。
需要对 json 进行解析/精简/重构等操作并将 json 作为 Terraform 的 datasource.
Json 的格式可能类似于这样:
[
{
"env_name": "dev",
"prom_url": "http://dev-prom.example.com",
"es_url": "http://dev-es.example.com:9200",
"jaeger_url": "http://dev-jaeger.example.com"
},
{
"env_name": "test",
"prom_url": "http://test-prom.example.com",
"es_url": "http://test-es.example.com:9200",
"jaeger_url": "http://test-jaeger.example.com"
}
]
解决方案:
- 构造一个 local 变量
- local 变量从 .json 文件中读取并内容并通过
jsondecode
+file
将 json 文件解码为 object - 使用
for
循环,将 object 根据当前需求调整,将例子中env
作为 key, 将其他作为 value - 批量创建资源时,通过
for_each
, 进行批量创建。
串起来, 最终如下:
locals {
# 将 json 文件转换为 对象
user_data = jsondecode(file("${path.module}/env-details.json"))
# 构造一个 map
# key 是 env_name
# value 又是一个 map, 其 key 是 grafana datasource type, value 是 url
envs = { for env in local.user_data : env.env_name =>
{
prometheus = env.prom_url
# 利用 ${} 构造新的 url
jaeger = "${env.jaeger_url}/trace/"
es = env.es_url
}
}
}
resource "grafana_data_source" "prometheus" {
# 通过 for_each 迭代
for_each = local.envs
type = "prometheus"
name = "${each.key}_prom"
uid = "${each.key}_prom"
url = each.value.prometheus
json_data_encoded = jsonencode({
httpMethod = "POST"
})
}
resource "grafana_data_source" "jaeger" {
for_each = local.envs
type = "jaeger"
name = "${each.key}_jaeger"
uid = "${each.key}_jaeger"
url = each.value.jaeger
}
resource "grafana_data_source" "elasticsearch" {
for_each = local.envs
type = "elasticsearch"
name = "${each.key}_es"
uid = "${each.key}_es"
url = each.value.es
database_name = "[example.*-]YYYY.MM.DD"
json_data_encoded = jsonencode({
esVersion = "6.0.0"
interval = "Daily"
includeFrozen = false
maxConcurrentShardRequests = 256
timeField = "@timestamp"
logLevelField = "level"
logMessageField = "message"
})
}
完成
️参考文档
- Overview - Configuration Language | Terraform | HashiCorp Developer
- Terraform: Using for-each in Terraform to iterate through local JSON (copyprogramming.com)
- automation - Iterate over Json using Terraform - Stack Overflow
- Using data returned by jsondecode and iterate over the results in a for_each loop - Terraform - HashiCorp Discuss
- How to Use Terraform's 'for_each', with Examples - The New Stack
三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.
Terraform 系列-使用 for-each 对本地 json 进行迭代的更多相关文章
- Terraform 系列-Terraform 简介
系列文章 Terraform 系列文章 前言 最近在使用 Terraform 来置备 OCI 的 Always Free Tier, 发现它非常好用.总结学习下:Terraform 的基础知识. 什么 ...
- 将Chrome调试器里的JavaScript变量保存成本地JSON文件
我写了一个系列的文章,主要用来搜集一些供程序员使用的小工具,小技巧,帮助大家提高工作效率. 推荐一个功能强大的文件搜索工具SearchMyFiles 介绍一个好用的免费流程图和UML绘制软件-Diag ...
- Chrome浏览器扩展开发系列之十四:本地消息机制Native messagin
Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 2016-11-24 09:36 114人阅读 评论(0) 收藏 举报 分类: PPAPI(27) 通过将浏览器 ...
- Terraform 系列-Terraform Cloud 比 Terraform OSS 有哪些增强?
系列文章 Terraform 系列文章 前言 最近在使用 Terraform Cloud 来置备 OCI 的 Always Free Tier, 发现它非常好用,相比 Terraform OSS, 用 ...
- 访问本地json文件因跨域导致的问题
我使用jquery的getJSON的方法获取本地的json文件,并进行操作,获取json 数据代码如下: $.getJSON("invite_panel.json",functio ...
- Angular 通过 $http.post 写入本地 JSON 文件
最近在练习使用 Angular,在实现 $http 对本地 JSON 文档读写的时候遇到了问题. 问题 使用 GET 方法成功将 JSON 文档的内容读出来:但是在使用 POST 插入本地 JSON ...
- 【Spring MVC系列】--(4)返回JSON
[Spring MVC系列]--(4)返回JSON 摘要:本文主要介绍如何在控制器中将数据生成JSON格式并返回 1.导入包 (1)spring mvc 3.0不需要任何其他配置,添加一个jackso ...
- Json.Net系列教程 2.Net类型与JSON的映射关系
原文 Json.Net系列教程 2.Net类型与JSON的映射关系 首先谢谢大家的支持和关注.本章主要介绍.Net类型与JSON是如何映射的.我们知道JSON中类型基本上有三种:值类型,数组和对象.而 ...
- 读取本地json文件,并转换为dictionary
// 读取本地JSON文件 - (NSDictionary *)readLocalFileWithName:(NSString *)name { // 获取文件路径 NSString *path = ...
- vue配置 请求本地json数据
第一步:在build文件夹下找到webpack.dev.conf.js文件,在const portfinder = require('portfinder')后添加 //第一步const expres ...
随机推荐
- 系统评价——层次分析法AHP的R语言实现(四)
对一个事物的评价往往会涉及多个因素或者多个指标,评价是在多个因素相互作用下的一个综合判断.多指标综合评价方法具有以下的特点:包含若干个指标,分别说明被评价对象的不同方面,评价方法最终要对被评价对象作出 ...
- R语言文本数据挖掘(四)
文本分词,就是对文本进行合理的分割,从而可以比较快捷地获取关键信息.例如,电商平台要想了解更多消费者的心声,就需要对消费者的文本评论数据进行内在信息的数据挖掘分析,而文本分词是文本挖掘的重要步骤.R语 ...
- Java内部类的使用介绍详解
前言 在之前讲解static静态内部类时,就给大家简单说过内部类的概念.但实际上,内部类并不是那么简单,所以今天我们需要对内部类进行专门地讲解和学习. 全文大约 [6500]字,不说废话,只讲可以让你 ...
- [ElasticSearch]#解决问题#修改Search Guard密码时 报错:ERR: Seems there is no Elasticsearch running on localhost:9300 - Will exit
问题复现 [root@es2 tools]# ps -ef | grep elasticsearch 9200 22693 1 1 09:31 ? 00:04:54 /usr/bin/java -Xm ...
- 四月十七日Java基础知识点
1.默认构造方法:如果class前面有public修饰符,则默认的构造方法也会是public的.由于系统提供的默认构造方法往往不能满足需求,所以用户可以自己定义类的构造方法来满足需要,一旦用户为该类定 ...
- 带你揭开神秘的javascript AST面纱之AST 基础与功能
作者:京东科技 周明亮 AST 基础与功能 在前端里面有一个很重要的概念,也是最原子化的内容,就是 AST ,几乎所有的框架,都是基于 AST 进行改造运行,比如:React / Vue /Taro ...
- 基础常用API总结2
String java.lang包下 返回值类型 方法 功能 boolean matches(String regex) 如果匹配当前字符串中regex(正则表达式)所表示的字符,如果有返回ture没 ...
- 【Vue2.x源码系列07】监听器watch原理
上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...
- [Pytorch框架] 2.5 循环神经网络
文章目录 2.5 循环神经网络 2.5.1 RNN简介 RNN的起因 为什么需要RNN RNN都能做什么 2.5.2 RNN的网络结构及原理 RNN LSTM GRU 2.5.3 循环网络的向后传播( ...
- PaddleDetection 快速上手
PaddleDetection 快速上手 本项目以路标数据集roadsign为例,详细说明了如何使用PaddleDetection训练一个目标检测模型,并对模型进行评估和预测. 本项目提供voc格式的 ...