Terraform数据源“archive_file”即使设置了depends_on,在计划阶段仍会对source_dir进行求值
环境:Terraform 1.14,archive provider 2.7.1
我在Terraform中有一个用于打包Lambda的流水线,流程如下:
- null_resource运行一个local-exec provisioner,将Python依赖安装到构建目录
- 一个wait-gate null_resource验证构建已完成(基于文件的握手)
- data "archive_file" 将构建目录打包成ZIP,并对wait gate设置依赖(depends_on)
- 通过local_existing_package将 ZIP路径传递给terraform-aws-modules/lambda
resource "null_resource" "build_lambda" {
triggers = {
source_hash = local.source_hash
}
provisioner "local-exec" {
interpreter = ["/bin/bash", "-euo", "pipefail", "-c"]
command = <<-EOT
rm -rf "${local.build_dir}"
mkdir -p "${local.build_dir}"
pip3 install requests -t "${local.build_dir}" --quiet
cp "${var.source_dir}/handler.py" "${local.build_dir}/"
date -u > "${local.build_dir}/.build_complete"
EOT
}
}
resource "null_resource" "wait_for_build" {
triggers = {
build_id = null_resource.build_lambda.id
}
provisioner "local-exec" {
interpreter = ["/bin/bash", "-euo", "pipefail", "-c"]
command = <<-EOT
if [[ ! -f "${local.build_dir}/.build_complete" ]]; then
echo "Error: build not complete" >&2
exit 1
fi
echo "Build verified"
EOT
}
}
data "archive_file" "lambda_package" {
type = "zip"
source_dir = local.build_dir
output_path = local.zip_path
depends_on = [null_resource.wait_for_build]
}
module "lambda" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 7.0"
create_package = false
local_existing_package = data.archive_file.lambda_package.output_path
# ...
}
问题出现在一个干净的CI工作区(Bitbucket Pipelines,之前没有状态文件,也没有.build/ 目录),执行terraform apply时失败,错误信息为:
Archive creation error with
module.lambda_module.data.archive_file.lambda_package
error creating archive: error archiving directory: could not archive
missing directory: /opt/atlassian/pipelines/agent/build/.build/lambda-package
我已经尝试了:
- data "archive_file" 并带有depends_on → 失败(计划阶段目录缺失)
- resource "archive_file" 并带有depends_on → 失败,所有哈希属性返回了 "Provider returned invalid result object after apply"
- 使用基于文件的握手的null_resource wait gate提供程序 → 同样失败
- 使用terraform_data代替null_resource → 同样失败
请指出原因并给出可行的修复思路。
解决方案
Terraform并非为这类 "先构建再部署" 的工作流而设计。
来自 hashicorp/archive 的 archive_file 数据源确实实现了这类工作流的一部分,但它的实现方式在本质上存在缺陷,因为Terraform期望数据源在计划阶段仅读取已经存在的信息,而不是创建新对象(磁盘上的文件)或直接对apply阶段生成的对象作出反应。archive_file 是对Terraform提供程序协议的错误实现,因此很难以可靠的方式使用它。
相反,我建议把Terraform仅用于此流水线的 “部署” 部分,并在Terraform之外解决构建部分,使用一个更合适的工具来根据源代码构建发布工件。
组装这样一条流水线有多种做法,但有一种可行的方法是,在AWS SSM Parameter Store中存放包含当前源代码版本的S3对象的名称,然后在你的Terraform配置中使用 aws_ssm_parameter 来获取它:
data "aws_ssm_parameter" "current_version" {
name = "current_lambda_package"
}
resource "aws_lambda_function" "example" {
s3_bucket = "example_lambda_packages"
s3_key = data.aws_ssm_parameter.current_version.value
}
之后,你可以让流水线的单独 build 步骤把构建好的包写入Amazon S3,然后更新 current_lambda_package 参数以指向它。
除了避免在Terraform内直接解决时出现的排序问题外,还有一个二级好处:如果在部署过程中或之后发现新版本有缺陷,你可以把 current_lambda_package 重置回之前的值,并再次运行 terraform apply 以回退到你之前正在运行的已知良好包。