【问题标题】:Can Terraform watch a directory for changes?Terraform 可以监视目录的更改吗?
【发布时间】:2018-12-10 19:53:30
【问题描述】:

我想监视一个文件目录,如果其中一个文件发生更改,则重新上传并运行其他一些任务。我之前的解决方案涉及监控单个文件,但这很容易出错,因为某些文件可能会被遗忘:

resource "null_resource" "deploy_files" {    
  triggers = {
    file1 = "${sha1(file("my-dir/file1"))}"
    file2 = "${sha1(file("my-dir/file2"))}"
    file3 = "${sha1(file("my-dir/file3"))}"
    # have I forgotten one?
  }

  # Copy files then run a remote script.
  provisioner "file" { ... }
  provisioner "remote-exec: { ... }
}

我的下一个解决方案是在一个资源中获取目录结构的哈希,并在第二个资源中使用此哈希作为触发器:

resource "null_resource" "watch_dir" {
  triggers = {
    always = "${uuid()}"
  }

  provisioner "local-exec" {
    command = "find my-dir  -type f -print0 | xargs -0 sha1sum | sha1sum > mydir-checksum"
  }
}


resource "null_resource" "deploy_files" {    
  triggers = {
    file1 = "${sha1(file("mydir-checksum"))}"
  }

  # Copy files then run a remote script.
  provisioner "file" { ... }
  provisioner "remote-exec: { ... }
}

这可以正常工作,除了对mydir-checksum 的更改仅在第一个apply 之后被拾取。所以我需要apply 两次,这不是很好。这有点杂乱无章。

我找不到更明显的方法来监视整个目录的内容更改。有标准的方法吗?

【问题讨论】:

  • 使用make 生成哈希然后将其用作触发器?因为我不知道任何其他解决方案。 find my-dir -type f -exec sha1sum + 也应该更快。
  • 您的意思是 make 作为在 terraform 之前运行的外部脚本?
  • 这是一个有趣的问题,我从来没有以这种方式使用过 Terraform。但我想知道这是否只是 Terraform 没有做的事情——它管理基础设施;文件系统处理似乎是二等公民。我可能会建议这是你想要一个围绕 Terraform 的包装脚本来塑造它以满足你的需要的地方。
  • @Joe 是的。您可以让make 任务检查并为您的目录生成 sum 文件,然后使用该文件内容作为触发器。
  • 谢谢两位。同意,这不是直接支持的事实可能是一个提示。但是有很多例子涉及观看文件和观看文件目录并没有什么不同。有问题的文件是 Docker Stack 部署文件和支持配置,我认为这些文件属于 Terraform 领域。

标签: terraform


【解决方案1】:

您可以使用"archive_file" data source:

data "archive_file" "init" {
  type        = "zip"
  source_dir = "data/"
  output_path = "data.zip"
}

resource "null_resource" "provision-builder" {
  triggers = {
    src_hash = "${data.archive_file.init.output_sha}"
  }

  provisioner "local-exec" {
    command = "echo Touché"
  }
}

当且仅当存档的哈希值发生更改时,才会重新配置空资源。只要source_dir(在本例中为data/)的内容发生变化,就会在刷新期间重建存档。

【讨论】:

  • 这真的很有趣!我认为这取决于输出压缩字节相对于输入的确定性/可重复性。这是偶然发生的事情还是定义了 zip 的稳定性?
  • 同意,我没有考虑过。幸运的是,我的实现没有向 zip 文件本身添加时间戳或任何内容,但即使是 压缩文件 的更新时间戳等内容也会使数据无效、更改哈希并使资源无效。跨度>
【解决方案2】:

在 Terraform 0.12 及更高版本中,可以使用 for expression 结合 fileset function 和哈希函数之一来计算目录中文件的组合校验和:

> sha1(join("", [for f in fileset(path.cwd, "*"): filesha1(f)]))
"77e0b2785eb7405ea5b3b610c33c3aa2dccb90ea"

上面的代码将为当前目录中与名称模式匹配的每个文件计算sha1 校验和,将校验和连接到一个字符串中,最后计算结果字符串的校验和。所以null_resource 的例子看起来像这样,上面的表达式作为触发器:

resource "null_resource" "deploy_files" {    
  triggers = {
    dir_sha1 = sha1(join("", [for f in fileset("my-dir", "*"): filesha1(f)]))
  }

  provisioner "file" { ... }
  provisioner "remote-exec: { ... }
}

注意fileset("my-dir", "*") 不考虑my-dir 子目录中的文件。如果您想包含这些校验和,请使用名称模式 ** 而不是 *

【讨论】:

  • 如果您正在访问上层目录中的文件,您还需要将其传递给 filesha1 函数。您可以使用本地来避免重复。例如:[for f in fileset("${path.module}/../website", "*"): filesha1("${path.module}/../website/${f}")]
  • 如果您对 tf 模块的内容执行此操作,您将如何从该操作中排除隐藏文件/状态文件?
【解决方案3】:

我也有同样的要求,我使用data.external资源以以下方式实现它:

  • 编写了一个脚本,它将使用md5sum 为我提供目录的校验和

    #!/bin/bash
    #
    # This script calculates the MD5 checksum on a directory
    #
    
    # Exit if any of the intermediate steps fail
    set -e
    
    # Extract "DIRECTORY" argument from the input into
    # DIRECTORY shell variables.
    # jq will ensure that the values are properly quoted
    # and escaped for consumption by the shell.
    eval "$(jq -r '@sh "DIRECTORY=\(.directory)"')"
    
    # Placeholder for whatever data-fetching logic your script implements
    CHECKSUM=`find ${DIRECTORY} -type f | LC_ALL=C sort | xargs shasum -a 256 | awk '{ n=split ($2, tokens, /\//); print $1 " " tokens[n]} ' |  shasum -a 256 | awk '{ print $1 }'`
    
    # Safely produce a JSON object containing the result value.
    # jq will ensure that the value is properly quoted
    # and escaped to produce a valid JSON string.
    jq -n --arg checksum "$CHECKSUM" '{"checksum":$checksum}'
    
  • 如下创建data.external

    data "external" "trigger" {
      program = ["${path.module}/dirhash.sh"]
    
      query {
        directory = "${path.module}/<YOUR_DIR_PATH_TO_WATCH>"
      }
    }
    
  • 使用上述资源results 输出作为null_resource 的触发器

    resource "null_resource" "deploy_files" {
      # Changes to any configuration file, requires the re-provisioning
      triggers {
        md5 = "${data.external.trigger.result["checksum"]}"
      }
      ...
    }
    

PS:脚本依赖jq

更新: 更新了校验和计算逻辑,以抵消不同平台上 find 的影响。

【讨论】:

  • 太棒了,非常感谢。我看到了外部资源的东西,然后我看到了 JSON 需求并忽略了它。我会试试这种方法。
  • @Joe,我已经更新了校验和逻辑以抵消整个平台的find 影响。请看一下。谢谢!
  • 我已经使用了一段时间了。它工作得非常好。干得好。
【解决方案4】:

我将它用于 GCP 中的云功能,其中云功能仅取决于文件名,因此我需要在任何文件更改时强制更改文件名,否则每次部署都会污染云功能。我使用本地解决了它,所以没有像archive_file 解决方案那样创建额外的文件。需要注意的是,您需要对文件名进行硬编码,这在某些用例中可能是可以接受的。

locals {
  # Hard code a list of files in the dir
  cfn_files = [
    "cfn/requirements.txt",
    "cfn/main.py",
  ]

  # Get the MD5 of each file in the directory
  cfn_md5sums = [for f in local.cfn_files : filemd5(f)]

  # Join the MD5 sums together and take the MD5 of all of them
  # Effectively checksumming the pieces of the dir you care about
  cfn_dirchecksum = md5(join("-", local.cfn_md5sums))
}
...

data "archive_file" "cfn" {
  type        = "zip"
  source_dir = "cfn"
  output_path = "cfn/build/${local.cfn_dirchecksum}.zip"
}
resource "google_storage_bucket_object" "archive" {
  name   = data.archive_file.cfn.output_path
  bucket = google_storage_bucket.cfn_bucket.name
  source = data.archive_file.cfn.output_path
}
...
resource "google_cloudfunctions_function" "function" {
  project     = var.project_id
  region      = var.region
  runtime     = "python37"

  source_archive_bucket = google_storage_bucket.cfn_bucket.name
  source_archive_object = google_storage_bucket_object.archive.name
...
}

【讨论】:

    【解决方案5】:

    Terraform 似乎没有提供任何目录树遍历功能,所以我能想到的唯一解决方案是使用某种外部工具来做到这一点,例如 Make:

    all: tf.plan
    
    tf.plan: hash *.tf
            terraform plan -o $@
    
    hash: some/dir
            find $^ -type f -exec sha1sum {} + > $@
    
    .PHONY: all hash
    

    然后在您的 Terraform 文件中:

    resource "null_resource" "deploy_files" {    
      triggers = {
        file1 = "${file("hash")}"
      }
    
      # Copy files then run a remote script.
      provisioner "file" { ... }
      provisioner "remote-exec: { ... }
    }
    

    【讨论】:

    • 感谢您的回答。我会考虑的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 1970-01-01
    • 2012-09-07
    • 2011-04-28
    • 1970-01-01
    • 2011-04-04
    • 2017-03-09
    相关资源
    最近更新 更多