从您的问题描述看来,描述您想要创建的内容的更直观的数据结构将是对象映射,其中键是存储桶名称,值描述该存储桶的设置:
variable "buckets" {
type = map(object({
source_file_path = string
key_prefix = string
}))
}
在您的 .tfvars 文件中定义存储桶时,现在将显示为具有复杂类型的单个定义:
buckets = {
bucket1 = {
source_file_path = "source1"
key_prefix = "path1"
}
bucket2 = {
source_file_path = "source2"
key_prefix = "path2"
}
}
这个数据结构每个bucket都有一个元素,所以适合直接用for_each作为描述bucket的资源:
resource "aws_s3_bucket" "example" {
for_each = each.buckets
bucket = each.key
# ...
}
有一个预先存在的官方模块hashicorp/dir/template,它已经封装了在目录前缀下查找文件的工作,根据文件名后缀为每个文件分配一个Content-Type,并可选地呈现模板。 (如果不需要,可以忽略模板功能,让目录只包含静态文件。)
每个存储桶都需要该模块的一个实例,因为每个存储桶都有自己的目录和一组文件,因此我们可以使用for_each chaining 告诉 Terraform 该模块的每个实例都与一个相关桶:
module "bucket_files" {
for_each = aws_s3_bucket.example
base_dir = var.buckets[each.key].source_file_path
}
模块文档显示how to map the result of the module to S3 bucket objects,但该示例仅适用于模块的单个实例。在您的情况下,我们需要一个额外的步骤来将其转换为跨所有存储桶的单个文件集合,which we can do using flatten:
locals {
bucket_files_flat = flatten([
for bucket_name, files_module in module.bucket_files : [
for file_key, file in files_module.files : {
bucket_name = bucket_name
local_key = file_key
remote_key = "${var.buckets[each.key].key_prefix}${file_key}"
source_path = file.source_path
content = file.content
content_type = file.content_type
etag = file.digests.md5
}
]
])
}
resource "aws_s3_bucket_object" "example" {
for_each = {
for bf in local.bucket_files_flat :
"s3://${bf.bucket_name}/${bf.remote_key}" => bf
}
# Now the rest of this is basically the same as
# the hashicorp/dir/template S3 example, but using
# the local.bucket_files_flat structure instead
# of the module result directly.
bucket = each.value.bucket_name
key = each.value.remote_key
content_type = each.value.content_type
# The template_files module guarantees that only one of these two attributes
# will be set for each file, depending on whether it is an in-memory template
# rendering result or a static file on disk.
source = each.value.source_path
content = each.value.content
# Unless the bucket has encryption enabled, the ETag of each object is an
# MD5 hash of that object.
etag = each.value.etag
}
Terraform 需要为aws_s3_bucket_object.example 的每个实例提供一个唯一的跟踪密钥,因此我只是随意地决定在这里使用s3:// URI 约定,因为我希望习惯于使用 S3 的人会熟悉它。这意味着资源块将声明具有如下地址的实例:
aws_s3_bucket_object.example["s3://bucket1/path1example.txt"]
aws_s3_bucket_object.example["s3://bucket2/path2other_example.txt"]
由于这些对象由它们在 S3 中的最终位置唯一标识,因此 Terraform 会将对文件的更改理解为就地更新,但对位置的任何更改都视为同时删除现有对象并添加新对象。
(我复制了这样一个事实,即您的示例只是将路径前缀与文件名连接起来而没有任何中间分隔符,这就是为什么它在上面显示为 path1example.txt 而不是 path1/example.txt。如果你想要斜杠,你可以将其添加到在local.bucket_files_flat 中定义remote_key 的表达式中。)