- IaC
- 云原生
- 基础设施
【免费下载链接】terraform-provider-aws
The AWS Provider enables Terraform to manage AWS resources.
导读
aws_s3_object是 Terraform AWS Provider 提供的一个数据源(Data Source),用于读取 S3 存储桶中对象的元数据,并在满足特定条件时可选地读取对象内容(body)。它常被用来把 S3 中存放的启动脚本注入 EC2 实例的user_data,或把 Lambda 函数包的version_id透传给aws_lambda_function,从而在配置间建立可靠的依赖关系。读完本文,你将掌握该数据源的参数与属性、body/body_base64的可用性边界、download_body与checksum_mode的用法,以及底层实现原理(基于HeadObject+GetObject的调用链)。
数据源概览:元数据为主,内容可选
aws_s3_object数据源的核心定位是:访问存储在 S3 存储桶中对象的元数据和(可选)内容。与资源型aws_s3_object(见 s3_object 资源文档)不同,数据源本身不创建、不修改任何对象,它只是把已有对象的属性暴露给 Terraform 配置使用。
从源码注册来看(internal/service/s3/object_data_source.go),该数据源以// @SDKDataSource("aws_s3_object", name="Object")注解声明,读操作使用无超时限制的ReadWithoutTimeout: dataSourceObjectRead:
// @SDKDataSource("aws_s3_object", name="Object") // @Tags(identifierAttribute="arn", resourceType="Object") func dataSourceObject() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceObjectRead, SchemaFunc: func() map[string]*schema.Schema { // ... }, } }数据源还通过@Tags(identifierAttribute="arn", resourceType="Object")声明支持读取对象标签(tags为 Computed 属性,使用tftags.TagsSchemaComputed()定义,见 object_data_source.go),并额外提供了aws_s3_object相关的标签读取测试(object_data_source_tags_gen_test.go)。
内容可读性限制:哪些Content-Type允许返回body
body字段并非对所有对象都可用。出于安全考虑,只有当对象的Content-Type属于人类可读类型时,数据源才会下载并返回正文,这是为了防止把不可打印的二进制字符注入 Terraform 状态,也避免下载大量最终只会被丢弃的数据。
文档明确列出了允许返回body的Content-Type白名单:
text/*(任意以text/开头的类型)application/jsonapplication/ld+jsonapplication/x-httpd-phpapplication/xhtml+xmlapplication/x-cshapplication/x-shapplication/xmlapplication/atom+xmlapplication/x-sqlapplication/yaml
源码中这段逻辑由isContentTypeAllowed()函数实现(internal/service/s3/object_data_source.go),它把上述白名单编译为正则表达式逐一匹配:
allowedContentTypes := []*regexp.Regexp{ regexache.MustCompile(`^application/atom\+xml$`), regexache.MustCompile(`^application/json$`), regexache.MustCompile(`^application/ld\+json$`), regexache.MustCompile(`^application/x-csh$`), regexache.MustCompile(`^application/x-httpd-php$`), regexache.MustCompile(`^application/x-sh$`), regexache.MustCompile(`^application/xhtml\+xml$`), regexache.MustCompile(`^application/xml$`), regexache.MustCompile(`^application/x-sql$`), regexache.MustCompile(`^application/yaml$`), regexache.MustCompile(`^text/.+`), }注意:当Content-Type为nil(对象未设置该头)时函数直接返回false;text/*使用^text/.+匹配,因此只要类型以text/开头并带后缀即可命中。代码注释还引用了 hashicorp/terraform#3858 的讨论,说明这是为了规避二进制文件与不可打印字符带来的状态污染问题。
示例用法
示例一:把文本对象内容注入 EC2 实例的 user_data
下面的示例读取一个文本对象(其Content-Type必须以text/开头),并把它的body用作 EC2 实例的user_data:
data "aws_s3_object" "bootstrap_script" { bucket = "ourcorp-deploy-config" key = "ec2-bootstrap-script.sh" } resource "aws_instance" "example" { instance_type = "t2.micro" ami = "ami-2757f631" user_data = data.aws_s3_object.bootstrap_script.body }这里aws_instance(完整参数说明见 instance 资源文档)的user_data直接引用数据源的body,Terraform 会先解析数据源完成 S3 读取,再创建 EC2 实例,天然形成了正确的依赖顺序。
示例二:读取 zip 包元数据,把最新 version_id 交给 Lambda
下面这个更复杂的示例只读取 S3 中 zip 文件的元数据,并把最新的version_id传给aws_lambda_function作为函数实现版本:
data "aws_s3_object" "lambda" { bucket = "ourcorp-lambda-functions" key = "hello-world.zip" } resource "aws_lambda_function" "test_lambda" { s3_bucket = data.aws_s3_object.lambda.bucket s3_key = data.aws_s3_object.lambda.key s3_object_version = data.aws_s3_object.lambda.version_id function_name = "lambda_function_name" role = aws_iam_role.iam_for_lambda.arn # (not shown) handler = "exports.test" }由于 zip 文件的Content-Type(通常是application/zip)不在白名单内,body不会被下载,数据源只返回元数据——这正是"只读元数据、避免下载大文件"设计意图的典型应用。aws_lambda_function的完整参数见 lambda_function 资源文档。
Argument Reference:数据源支持的参数
数据源支持以下参数:
| 参数 | 是否必填 | 说明 |
|---|---|---|
bucket | Required | 要读取对象的存储桶名称;也可以指定为 S3 access point 的 ARN |
key | Required | 对象在桶内的完整路径 |
checksum_mode | Optional | 需要检索对象校验和时设为ENABLED。启用后若对象使用 KMS 加密,你还必须拥有kms:Decrypt权限。合法值:ENABLED |
download_body | Optional | 设为true时总是把对象数据下载到body_base64属性。不设置时,若满足上文 内容可读性限制 的条件,body可用而body_base64不可用;设为false时完全不下载正文,body与body_base64均不可用,可提升性能 |
range | Optional | 要检索的对象字节范围,格式遵循 HTTPRange头 规范 |
region | Optional | 资源被管理的区域,默认使用 provider 配置 中设置的区域 |
version_id | Optional | 返回对象的指定版本 ID(默认为最新版本) |
源码视角:参数如何进入请求
从 dataSourceObjectRead 的实现可以看到,这些参数被组装进HeadObjectInput:
input := s3.HeadObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), } if v, ok := d.GetOk("checksum_mode"); ok { input.ChecksumMode = types.ChecksumMode(v.(string)) } if v, ok := d.GetOk("range"); ok { input.Range = aws.String(v.(string)) } if v, ok := d.GetOk("version_id"); ok { input.VersionId = aws.String(v.(string)) }几个值得注意的细节:
bucket可以是 Access Point ARN:当bucket是 ARN 且客户端 Region 为aws-global时,源码会自动追加o.UseARNRegion = true选项,以避免 "Invalid configuration: region from ARN ... does not match client regionaws-global" 报错(object_data_source.go)。测试用例TestAccS3ObjectDataSource_basicViaAccessPoint(object_data_source_test.go)验证了通过 Access Point ARN 读取的能力。checksum_mode有枚举校验:schema 中通过enum.Validate[types.ChecksumMode]()做校验(object_data_source.go),非ENABLED值会直接报错;测试TestAccS3ObjectDataSource_checksumMode(object_data_source_test.go)验证了该模式的行为。download_body是可空布尔:schema 使用nullable.TypeNullableBool定义(object_data_source.go),支持 "设置 / 不设置 / 显式 null" 三种状态,这正是它语义上不同于普通布尔的原因。range同时作用于 HEAD 与 GET:range不仅传入HeadObjectInput,在下载阶段还会传入GetObjectInput,实现字节范围的裁剪(object_data_source.go)。
key 的清洗规则
在发起请求前,key会经过sdkv1CompatibleCleanKey()处理(internal/service/s3/object.go)。该函数为了与 AWS SDK for Go v1 的自动 URI 清理行为保持向后兼容,会:
- 去掉开头的
./; - 忽略所有开头的
/(strings.TrimLeft(key, "/")); - 把连续的多个
/折叠为单个/。
func sdkv1CompatibleCleanKey(key string) string { key = strings.TrimPrefix(key, "./") key = strings.TrimLeft(key, "/") key = regexache.MustCompile(`/+`).ReplaceAllString(key, "/") return key }因此文档末尾的 Note 是完全准确的:Terraform 忽略对象key中所有开头的/,并把其余部分的连续多个/视为单个/,所以/index.html与index.html指向同一个 S3 对象,first//second///third//与first/second/third/也等价。仓库中有一系列测试专门覆盖这些边界场景(object_data_source_test.go):
TestAccS3ObjectDataSource_leadingSlash(开头单个斜杠)TestAccS3ObjectDataSource_multipleSlashes(中间多个斜杠)TestAccS3ObjectDataSource_singleSlashAsKey(key 仅为单个斜杠)TestAccS3ObjectDataSource_leadingDotSlash(开头./)TestAccS3ObjectDataSource_leadingMultipleSlashes(开头多个斜杠)
Attribute Reference:数据源导出的属性
除上述参数外,数据源还会导出以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
arn | string | 对象的 ARN |
body | string | 对象数据(可用性受上文 限制 约束;若download_body为false则不可用) |
body_base64 | string | 以 Base64 编码字符串表示的对象数据。仅在download_body设为true时可用 |
bucket_key_enabled | bool | 是否为 SSE-KMS 使用 Amazon S3 Bucket Keys |
cache_control | string | 沿请求/响应链的缓存行为 |
checksum_crc32 | string | 对象的 Base64 编码 32 位 CRC32 校验和 |
checksum_crc32c | string | 对象的 Base64 编码 32 位 CRC32C 校验和 |
checksum_crc64nvme | string | 对象的 Base64 编码 64 位 CRC64NVME 校验和 |
checksum_sha1 | string | 对象的 Base64 编码 160 位 SHA-1 摘要 |
checksum_sha256 | string | 对象的 Base64 编码 256 位 SHA-256 摘要 |
content_disposition | string | 对象的呈现信息 |
content_encoding | string | 已应用于对象的内容编码,以及为获得 Content-Type 头所指媒体类型而必须应用的解码机制 |
content_language | string | 内容使用的语言 |
content_length | int | 正文大小(字节) |
content_type | string | 描述对象数据格式的标准 MIME 类型 |
etag | string | 为对象生成的 ETag(未加密时是对象内容的 MD5 和) |
expiration | string | 若配置了对象过期(见 对象生命周期管理),该字段包含此头,内含expiry-date和rule-id键值对(rule-id值经 URL 编码) |
expires | string | 对象不再可缓存的日期和时间 |
last_modified | string | 对象最后修改日期,RFC1123 格式(如Mon, 02 Jan 2006 15:04:05 MST) |
metadata | map | 存储在 S3 对象上的元数据映射,键 一律以小写返回 |
object_lock_legal_hold_status | string | 对象是否处于生效的 legal hold 状态;仅在你拥有查看对象 legal hold 状态权限时返回 |
object_lock_mode | string | 当前对该对象生效的对象锁 保留模式 |
object_lock_retain_until_date | string | 该对象对象锁到期的日期和时间 |
server_side_encryption | string | 若对象使用服务端加密(KMS 或 Amazon S3 托管加密密钥)存储,此字段包含所选加密方式与算法 |
sse_kms_key_id | string | 若存在,指定用于该对象的 KMS 主加密密钥 ID |
storage_class | string | 对象的存储类别信息;除Standard存储类对象外均可用 |
tags | map | 分配给对象的标签映射 |
version_id | string | 返回对象的最新版本 ID |
website_redirect_location | string | 若桶配置为网站,将请求重定向到同桶其他对象或外部 URL;S3 将该头值存于对象元数据 |
属性映射细节:从 HEAD 响应到状态
源码中属性大多直接取自HeadObject的输出(object_data_source.go),但有几点值得展开:
etag去除引号:源码用strings.Trim(aws.ToString(output.ETag),")去掉 ETag 首尾的引号,注释引用了 AWS 论坛关于该行为的讨论(object_data_source.go)。last_modified固定 RFC1123 格式:通过output.LastModified.Format(time.RFC1123)输出;测试用正则^[A-Za-z]{3}, [0-9]+ [A-Za-z]+ [0-9]{4} [0-9:]+ [A-Z]+$校验格式(object_data_source_test.go)。storage_class的特殊处理:由于STANDARD(默认)存储类在 HEAD 响应中不会出现,源码在output.StorageClass == ""时显式回填为types.ObjectStorageClassStandard(object_data_source.go)。metadata键强制小写:AWS 会规范化元数据键为小写,文档明确说明返回的键一律小写;测试TestAccS3ObjectDataSource_metadataUppercaseKey(object_data_source_test.go)专门验证了使用大写元数据键时的行为。- 对象锁相关属性:
object_lock_retain_until_date经flattenObjectDate转换为 RFC3339 格式(object.go);对应测试见TestAccS3ObjectDataSource_objectLockLegalHoldOn/Off(object_data_source_test.go)。
底层原理:HeadObject + GetObject 的读取流程
数据源的完整读取流程可以分为三个阶段,全部在dataSourceObjectRead中完成(object_data_source.go):
1. 构造并发送HeadObject请求
findObject(internal/service/s3/object.go)封装了对 S3 API 的HeadObject调用:当 HTTP 状态码为 404 时返回retry.NotFoundError,输出为空时返回tfresource.NewEmptyResultError()。若响应中的DeleteMarker为真,则直接报错 "S3 Bucket (...) Object (...) has been deleted"(object_data_source.go),表示对象实际已被删除标记覆盖。
2. 组装数据源 ID 与 ARN
数据源的 ID 由bucket + "/" + key构成,若指定了version_id则追加"@" + version_id(如my-bucket/path/key@versionId)。ARN 通过newObjectARN生成并写入arn属性。
3. 按需下载正文
这是最关键的分支逻辑(object_data_source.go):
downloadBody, downloadBodyNull, err := nullable.Bool(d.Get("download_body").(string)).ValueBool() // skip object download if download_body is explicitly set to false if !downloadBodyNull && !downloadBody { return diags } if downloadBody || isContentTypeAllowed(output.ContentType) { downloader := manager.NewDownloader(conn, manager.WithDownloaderClientOptions(optFns...)) buf := manager.NewWriteAtBuffer(make([]byte, 0)) // ... 组装 GetObjectInput(Bucket/Key/VersionId/Range) _, err = downloader.Download(ctx, buf, &inputObjectDownload) // ... if downloadBody { base64Body := base64.StdEncoding.EncodeToString(buf.Bytes()) d.Set("body_base64", base64Body) } if isContentTypeAllowed(output.ContentType) { d.Set("body", string(buf.Bytes())) } }由此可以总结出body/body_base64的完整可用性矩阵:
download_body取值 | body | body_base64 | 是否发起 GetObject 下载 |
|---|---|---|---|
false | 不可用 | 不可用 | 否(直接返回) |
true | 仅当 Content-Type 在白名单内时可用 | 始终可用(Base64) | 是 |
| 未设置 / null | 仅当 Content-Type 在白名单内时可用 | 不可用 | 仅当 Content-Type 在白名单内时 |
测试用例TestAccS3ObjectDataSource_body_base64(object_data_source_test.go)完整覆盖了这四种组合:不设置download_body时body/body_base64都为空;设为true时body_base64等于Hello World的 Base64 编码;设为false时两者都不可用;不设置但 Content-Type 可读时body为原始文本而body_base64为空。
目录桶(Directory Bucket)支持
源码对目录桶做了专门适配:当bucket名匹配目录桶命名规则(形如example--usw2-az2--x-s3,见 internal/service/s3/directory_bucket.go)时,会切换到S3ExpressClient客户端(object_data_source.go)。对应测试TestAccS3ObjectDataSource_directoryBucket(object_data_source_test.go)验证了这一路径。
总结与最佳实践
- 优先把该数据源当作"元数据读取器":
bucket、key两个必填参数即可完成 HEAD 请求;需要内容时再考虑body/body_base64,并注意 Content-Type 白名单约束。 - 用
download_body = true显式获取二进制内容:zip、图片等二进制对象无法通过body获取,显式开启download_body后使用body_base64是唯一途径。 - 用
download_body = false优化性能:当只需要元数据(如version_id、etag、storage_class)时,显式关闭下载可避免不必要的流量开销。 - 善用
version_id建立版本依赖:结合aws_lambda_function的s3_object_version,可以实现"函数包更新即触发 Lambda 更新"的经典模式。 - 注意
key清洗与metadata键小写化:/index.html与index.html等价;元数据键返回时均为小写,编写lookup()或for表达式时要有所预期。 - 校验和需要显式开启:
checksum_crc32、checksum_sha256等属性仅在设置checksum_mode = "ENABLED"时才会从 S3 返回;若对象使用 KMS 加密,还需具备kms:Decrypt权限。
如需进一步了解对象相关的其他数据源与资源,可继续阅读 aws_s3_objects(批量列出对象)、aws_s3_object 资源 与 aws_s3_object_copy 资源 的文档。
- IaC
- 云原生
- 基础设施
【免费下载链接】terraform-provider-aws
The AWS Provider enables Terraform to manage AWS resources.
相关推荐
Terraform AWS Provider 数据源 `aws_s3_bucket_replication_configuration` 完整指南:读取 S3 桶复制配置
Terraform AWS Provider 数据源 aws_s3_bucket_replication_configuration 完整指南:读取 S3 桶复
IaC云原生基础设施Terraform AWS Provider 数据源 aws_kms_custom_key_store:读取 KMS 自定义密钥库元数据的完整指南
Terraform AWS Provider 数据源 aws_kms_custom_key_store:读取 KMS 自定义密钥库元数据的完整指南 导读 aws
IaC云原生基础设施使用 Terraform AWS Provider 的 aws_glue_catalog_table 数据源读取 AWS Glue Data Catalog 表元数据
使用 Terraform AWS Provider 的 aws_glue_catalog_table 数据源读取 AWS Glue Data Catalog 表
IaC云原生基础设施
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考